From 3d2c9125fb22470b43dca5b65bb69d7237a468e4 Mon Sep 17 00:00:00 2001 From: Joseph Brothers Date: Wed, 28 Aug 2024 14:29:13 -0600 Subject: [PATCH 01/24] run script from command line and wait, monitoring status --- openc3/bin/openc3cli | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/openc3/bin/openc3cli b/openc3/bin/openc3cli index 30972d6ca..56b0f6e8a 100755 --- a/openc3/bin/openc3cli +++ b/openc3/bin/openc3cli @@ -64,6 +64,8 @@ def print_usage puts " cli help # Displays this information" puts " cli rake # Runs rake in the local directory" puts " cli irb # Runs irb in the local directory" + puts " cli script # Starts named script and waits for it to complete" + puts " OPTION: --MAX_SECONDS overrides OPENC3_SCRIPT_AWAIT if present" puts " cli validate /PATH/FILENAME.gem SCOPE variables.txt # Validate a COSMOS plugin gem file" puts " cli load /PATH/FILENAME.gem SCOPE variables.txt # Loads a COSMOS plugin gem file" puts " cli list # Lists installed plugins, SCOPE is DEFAULT if not given" @@ -648,6 +650,27 @@ def run_bridge(filename, params) end end +def await_script(args=[]) + # REVIEW: are we limiting the wait for status, or the script's execution? + # do we want 0 to mean 'forever'? + # do we want to accept suite, scope, and environment options? + wait_limit = ENV['OPENC3_SCRIPT_AWAIT'].to_i + if args[0] =~ /^--d*/ + wait_limit = args[0][2..-1].to_i + args.shift + end + # REVIEW: pretty sure there's no way to set OPENC3_SCRIPT_API_TIMEOUT through the api + # but I guess that's not what we want here anyway + require 'openc3/script' + # REVIEW: is there a case to be made for requiring script/ once at the top of this file? + id = script_run(args[0], disconnect: false, environment: nil, scope: 'DEFAULT') + OpenC3::RunningScriptWebSocketApi.new(id: id) do |api| + while true + puts api.read + end + end +end + if not ARGV[0].nil? # argument(s) given # Handle each task @@ -657,6 +680,9 @@ if not ARGV[0].nil? # argument(s) given ARGV.clear IRB.start + when 'script' + await_script(ARGV[1..-1]) + when 'rake' if File.exist?('Rakefile') puts `rake #{ARGV[1..-1].join(' ')}` From a20fac609aa0eabb5e5ca949947ccb5436814216 Mon Sep 17 00:00:00 2001 From: Joseph Brothers Date: Wed, 28 Aug 2024 17:11:12 -0600 Subject: [PATCH 02/24] fix crash when seconds option provided --- .../app/models/running_script.rb | 2 +- openc3/bin/openc3cli | 49 +++++++++++++++---- 2 files changed, 40 insertions(+), 11 deletions(-) diff --git a/openc3-cosmos-script-runner-api/app/models/running_script.rb b/openc3-cosmos-script-runner-api/app/models/running_script.rb index e49058ba1..d2de85c5c 100644 --- a/openc3-cosmos-script-runner-api/app/models/running_script.rb +++ b/openc3-cosmos-script-runner-api/app/models/running_script.rb @@ -430,7 +430,7 @@ def initialize(id, scope, name, disconnect) breakpoints = @@breakpoints[filename]&.filter { |_, present| present }&.map { |line_number, _| line_number - 1 } # -1 because frontend lines are 0-indexed breakpoints ||= [] OpenC3::Store.publish(["script-api", "running-script-channel:#{@id}"].compact.join(":"), - JSON.generate({ type: :file, filename: @filename, scope: @scope, text: @body.to_utf8, breakpoints: breakpoints })) + JSON.generate({ type: :file, filename: @filename, scope: @scope, text: @body&.to_utf8, breakpoints: breakpoints })) if (@body =~ SUITE_REGEX) # Process the suite file in this context so we can load it # TODO: Do we need to worry about success or failure of the suite processing? diff --git a/openc3/bin/openc3cli b/openc3/bin/openc3cli index 56b0f6e8a..0f7479858 100755 --- a/openc3/bin/openc3cli +++ b/openc3/bin/openc3cli @@ -65,7 +65,7 @@ def print_usage puts " cli rake # Runs rake in the local directory" puts " cli irb # Runs irb in the local directory" puts " cli script # Starts named script and waits for it to complete" - puts " OPTION: --MAX_SECONDS overrides OPENC3_SCRIPT_AWAIT if present" + puts " OPTION: --MAX_SECONDS monitoring status before it detaches from the running script" puts " cli validate /PATH/FILENAME.gem SCOPE variables.txt # Validate a COSMOS plugin gem file" puts " cli load /PATH/FILENAME.gem SCOPE variables.txt # Loads a COSMOS plugin gem file" puts " cli list # Lists installed plugins, SCOPE is DEFAULT if not given" @@ -650,23 +650,52 @@ def run_bridge(filename, params) end end +def await_dotter(script_id) + OpenC3::RunningScriptWebSocketApi.new(id: script_id) do |api| + while (resp = api.read) do + case resp['type'] + when 'file' + puts resp.pretty_inspect + when 'line' + fn = resp['filename'].nil? ? '' : resp['filename'] + puts "At [#{fn}:#{resp['line_no']}] state [#{resp['state']}]" + when 'output' + puts resp['line'] + when 'time' + puts resp.pretty_inspect + when 'complete' + puts 'script complete' + break + else + puts resp + end + end + end +end + def await_script(args=[]) - # REVIEW: are we limiting the wait for status, or the script's execution? - # do we want 0 to mean 'forever'? - # do we want to accept suite, scope, and environment options? - wait_limit = ENV['OPENC3_SCRIPT_AWAIT'].to_i + # we are limiting the wait for status, not the scipt run time + # we want 0 to mean 'forever' + # REVIEW: do we want to accept suite, scope, and environment options at the cli? + wait_limit = 0 if args[0] =~ /^--d*/ wait_limit = args[0][2..-1].to_i args.shift end - # REVIEW: pretty sure there's no way to set OPENC3_SCRIPT_API_TIMEOUT through the api - # but I guess that's not what we want here anyway require 'openc3/script' # REVIEW: is there a case to be made for requiring script/ once at the top of this file? id = script_run(args[0], disconnect: false, environment: nil, scope: 'DEFAULT') - OpenC3::RunningScriptWebSocketApi.new(id: id) do |api| - while true - puts api.read + if (wait_limit < 1) then + await_dotter(id) + else + Timeout::timeout(wait_limit, nil, "MAX_SECONDS #{wait_limit} exceeded") do + await_dotter(id) + rescue Timeout::Error => te # also thrown by Websocket API, so we check + if te.message =~ /^MAX_SECONDS / + puts te.message+", detaching from running script #{args[0]}" + else + raise + end end end end From fcff7aa819db9b42f451cbb6e78fe62b272aa504 Mon Sep 17 00:00:00 2001 From: Joseph Brothers Date: Wed, 28 Aug 2024 17:17:53 -0600 Subject: [PATCH 03/24] error exit if no script name provided --- openc3/bin/openc3cli | 1 + 1 file changed, 1 insertion(+) diff --git a/openc3/bin/openc3cli b/openc3/bin/openc3cli index 0f7479858..1ff8e98e4 100755 --- a/openc3/bin/openc3cli +++ b/openc3/bin/openc3cli @@ -682,6 +682,7 @@ def await_script(args=[]) wait_limit = args[0][2..-1].to_i args.shift end + abort("No script file provided") if args[0].nil? require 'openc3/script' # REVIEW: is there a case to be made for requiring script/ once at the top of this file? id = script_run(args[0], disconnect: false, environment: nil, scope: 'DEFAULT') From 79823d4d59f32a4f47fe56fcb22b40f6a1f28788 Mon Sep 17 00:00:00 2001 From: Joseph Brothers Date: Wed, 28 Aug 2024 17:30:22 -0600 Subject: [PATCH 04/24] te was correct and meaningful, but CodeSpell, so tmo timeout excp exception --- openc3/bin/openc3cli | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openc3/bin/openc3cli b/openc3/bin/openc3cli index 1ff8e98e4..46b7bc531 100755 --- a/openc3/bin/openc3cli +++ b/openc3/bin/openc3cli @@ -691,9 +691,9 @@ def await_script(args=[]) else Timeout::timeout(wait_limit, nil, "MAX_SECONDS #{wait_limit} exceeded") do await_dotter(id) - rescue Timeout::Error => te # also thrown by Websocket API, so we check - if te.message =~ /^MAX_SECONDS / - puts te.message+", detaching from running script #{args[0]}" + rescue Timeout::Error => tmoexcp # also thrown by Websocket API, so we check + if tmoexcp.message =~ /^MAX_SECONDS / + puts tmoexcp.message+", detaching from running script #{args[0]}" else raise end From 9ab7561d9b1924da5095310e40c07bd46bca8103 Mon Sep 17 00:00:00 2001 From: Joseph Brothers Date: Wed, 28 Aug 2024 18:10:43 -0600 Subject: [PATCH 05/24] CodeSpell, timeout, and exit exception --- openc3/bin/openc3cli | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openc3/bin/openc3cli b/openc3/bin/openc3cli index 46b7bc531..3371d3a82 100755 --- a/openc3/bin/openc3cli +++ b/openc3/bin/openc3cli @@ -669,12 +669,13 @@ def await_dotter(script_id) else puts resp end + sleep(2) end end end def await_script(args=[]) - # we are limiting the wait for status, not the scipt run time + # we are limiting the wait for status, not the script run time # we want 0 to mean 'forever' # REVIEW: do we want to accept suite, scope, and environment options at the cli? wait_limit = 0 @@ -691,6 +692,7 @@ def await_script(args=[]) else Timeout::timeout(wait_limit, nil, "MAX_SECONDS #{wait_limit} exceeded") do await_dotter(id) + # Timeout::ExitException rescue Timeout::Error => tmoexcp # also thrown by Websocket API, so we check if tmoexcp.message =~ /^MAX_SECONDS / puts tmoexcp.message+", detaching from running script #{args[0]}" From 0bac9c8328126b1f5f0420a6676b4883a9ab9a94 Mon Sep 17 00:00:00 2001 From: Joseph Brothers Date: Thu, 29 Aug 2024 20:47:10 -0600 Subject: [PATCH 06/24] more options and env parms for ARGV, and more message types and states for the waiter --- .../app/models/running_script.rb | 3 +- openc3/bin/openc3cli | 133 +++++++++++++++--- 2 files changed, 112 insertions(+), 24 deletions(-) diff --git a/openc3-cosmos-script-runner-api/app/models/running_script.rb b/openc3-cosmos-script-runner-api/app/models/running_script.rb index d2de85c5c..0b5e047b9 100644 --- a/openc3-cosmos-script-runner-api/app/models/running_script.rb +++ b/openc3-cosmos-script-runner-api/app/models/running_script.rb @@ -427,10 +427,11 @@ def initialize(id, scope, name, disconnect) # Retrieve file @body = ::Script.body(@scope, name) + raise "nothing runnable in scope: [#{@scope} for name: [#{name}]]" if @body.nil? breakpoints = @@breakpoints[filename]&.filter { |_, present| present }&.map { |line_number, _| line_number - 1 } # -1 because frontend lines are 0-indexed breakpoints ||= [] OpenC3::Store.publish(["script-api", "running-script-channel:#{@id}"].compact.join(":"), - JSON.generate({ type: :file, filename: @filename, scope: @scope, text: @body&.to_utf8, breakpoints: breakpoints })) + JSON.generate({ type: :file, filename: @filename, scope: @scope, text: @body.to_utf8, breakpoints: breakpoints })) if (@body =~ SUITE_REGEX) # Process the suite file in this context so we can load it # TODO: Do we need to worry about success or failure of the suite processing? diff --git a/openc3/bin/openc3cli b/openc3/bin/openc3cli index 3371d3a82..9ebb83717 100755 --- a/openc3/bin/openc3cli +++ b/openc3/bin/openc3cli @@ -33,6 +33,7 @@ require 'openc3/models/gem_model' require 'openc3/models/migration_model' require 'openc3/models/tool_model' require 'openc3/packets/packet_config' +require 'openc3/script' require 'openc3/bridge/bridge' require 'ostruct' require 'optparse' @@ -58,14 +59,25 @@ MIGRATE_PARSER = OptionParser.new do |opts| end ERROR_CODE = 1 +CLI_Script_actions = %w(help list run wait) +Dash_num_pattern=/^--?(\d+)/ # one or two dashes followed by at least one digit, capturing the digits +$script_left_waiting = '' +trap('INT') do + abort("Interrupted at console; exiting.#{$script_left_waiting}") +end + # Prints the usage text for the openc3cli executable def print_usage puts "Usage:" puts " cli help # Displays this information" puts " cli rake # Runs rake in the local directory" puts " cli irb # Runs irb in the local directory" - puts " cli script # Starts named script and waits for it to complete" - puts " OPTION: --MAX_SECONDS monitoring status before it detaches from the running script" + puts " cli script run NAME SCOPE variable1=value1 variable2=value2 # Starts named script" + puts " cli script wait NAME SCOPE variable1=value1 variable2=value2 # Starts named script showing status on console,\ + default until error or exit" + puts " PARAMETERS name-value pairs to form the script's runtime environment" + puts " OPTIONS: --MAX_SECONDS to show status before detaching from the running script; ie --100" + puts " --disconnect will run the script without connecting" puts " cli validate /PATH/FILENAME.gem SCOPE variables.txt # Validate a COSMOS plugin gem file" puts " cli load /PATH/FILENAME.gem SCOPE variables.txt # Loads a COSMOS plugin gem file" puts " cli list # Lists installed plugins, SCOPE is DEFAULT if not given" @@ -392,7 +404,6 @@ def list_plugins(scope:) if $openc3_in_cluster names = OpenC3::PluginModel.names(scope: scope) else - require 'openc3/script' names = plugin_list(scope: scope) end names.each do |name| @@ -494,8 +505,6 @@ def load_plugin(plugin_file_path, scope:, plugin_hash_file: nil, force: false) end else # Outside Cluster - require 'openc3/script' - if plugin_hash_file plugin_hash = JSON.parse(File.read(plugin_hash_file), :allow_nan => true, :create_additions => true) else @@ -523,7 +532,6 @@ def unload_plugin(plugin_name, scope:) end else # Outside Cluster - require 'openc3/script' process_name = plugin_uninstall(plugin_name, scope: scope) print "Uninstalling..." wait_process_complete(process_name) @@ -541,7 +549,6 @@ def cli_pkg_install(filename, scope:) end else # Outside Cluster - require 'openc3/script' process_name = package_install(filename, scope: scope) print "Installing..." wait_process_complete(process_name) @@ -559,7 +566,6 @@ def cli_pkg_uninstall(filename, scope:) end else # Outside Cluster - require 'openc3/script' process_name = package_uninstall(filename, scope: scope) if File.extname(filename) == '.rb' puts "Uninstalled" @@ -653,49 +659,68 @@ end def await_dotter(script_id) OpenC3::RunningScriptWebSocketApi.new(id: script_id) do |api| while (resp = api.read) do + # see ScriptRunner.vue for types and states case resp['type'] - when 'file' + when 'running', 'breakpoint', 'waiting' puts resp.pretty_inspect + when 'error', 'fatal' + $script_left_waiting = '' + puts 'script failed' + break + when 'file' + puts "Filename #{resp['filename']} scope #{resp['scope']}" when 'line' fn = resp['filename'].nil? ? '' : resp['filename'] puts "At [#{fn}:#{resp['line_no']}] state [#{resp['state']}]" + if resp['state'] == 'error' + $script_left_waiting = '' + puts 'script failed' + break + end when 'output' puts resp['line'] + when 'paused' + if resp['state'] == 'fatal' + $script_left_waiting = '' + puts 'script failed' + break + end when 'time' puts resp.pretty_inspect when 'complete' + $script_left_waiting = '' puts 'script complete' break else - puts resp + puts resp.pretty_inspect end - sleep(2) end end end -def await_script(args=[]) +def cli_script_await(disconnect=false, environment={}, args=[]) # we are limiting the wait for status, not the script run time - # we want 0 to mean 'forever' - # REVIEW: do we want to accept suite, scope, and environment options at the cli? + # we make 0 mean 'forever' wait_limit = 0 - if args[0] =~ /^--d*/ - wait_limit = args[0][2..-1].to_i + if (match = args[0].match(Dash_num_pattern)) + wait_limit = match.captures[0].to_i + puts "Will use MAX_SECONDS [#{match.captures[0]}]" args.shift end abort("No script file provided") if args[0].nil? - require 'openc3/script' - # REVIEW: is there a case to be made for requiring script/ once at the top of this file? - id = script_run(args[0], disconnect: false, environment: nil, scope: 'DEFAULT') + scope = args[1] + scope ||= 'DEFAULT' + id = script_run(args[0], disconnect: disconnect, environment: environment, scope: scope) # could raise + $script_left_waiting = " Script #{args[0]} still running remotely.\n" # for Ctrl-C if (wait_limit < 1) then await_dotter(id) else Timeout::timeout(wait_limit, nil, "MAX_SECONDS #{wait_limit} exceeded") do await_dotter(id) - # Timeout::ExitException - rescue Timeout::Error => tmoexcp # also thrown by Websocket API, so we check + rescue Timeout::ExitException, Timeout::Error => tmoexcp + # Timeout exceptions are also thrown by the Websocket API, so we check if tmoexcp.message =~ /^MAX_SECONDS / - puts tmoexcp.message+", detaching from running script #{args[0]}" + puts tmoexcp.message+", detaching from running script #{args[0]}" else raise end @@ -703,6 +728,59 @@ def await_script(args=[]) end end +def cli_script_run(disconnect=false, environment={}, args=[]) + if (match = args[0].match(Dash_num_pattern)) # -11, --11, and 11 are probably not a script file name + wrong_wait = match.captures[0] + wrong_wait ||= '0' + abort("Did you mean \"script wait --#{wrong_wait.to_i} #{args[1]}\"?") + end + abort("No script file provided") if args[0].nil? + # heaven help you if you left out the script name + scope = args[1] + scope ||= 'DEFAULT' + if (id = script_run(args[0], disconnect: disconnect, environment: environment, scope: scope)) + puts "Script #{args[0]} started remotely." + await_dotter(id) + else + puts "\nFailed to start #{args[0]}" + end +end + +## cli_script(args) turns an ARGV of [run|wait] < --123... > < --disconnect > SCRIPT +# into function calls and tidied parameters to remote-control RunningScriptWebSocketApi +def cli_script(args=[]) + # handled lower down #scope ||= 'DEFAULT' + check_environment() + + command = args[0] + args.shift + # peel off the command + discon = args.delete('--disconnect'){true} + discon ||= false + # pull out the disconnect flag + environ = {} + args.each do |arg| + name, value = arg.split('=') + if name and value + environ[name] = value + args.delete(arg) + # add env[k]=v ; pull out "k=v" + end + end + case command + # args[] should now be ["--100", "script_name", ""] + # or ["--100", "script_name"] + # or ["script_name", ""] + # or ["script_name"] + when 'run' + cli_script_run(discon, environ, args) + when 'wait' + cli_script_wait(discon, environ, args) + else + abort 'openc3cli internall error: parsing arguments' + end +end + if not ARGV[0].nil? # argument(s) given # Handle each task @@ -713,7 +791,16 @@ if not ARGV[0].nil? # argument(s) given IRB.start when 'script' - await_script(ARGV[1..-1]) + case ARGV[1] + when 'list' + # list, list running, and list completed are future features + abort ("[#{ARGV[1]}] not implemented") + when 'run', 'wait' + cli_script(ARGV[1..-1]) + else + # invalid actions, misplaced and malformed leading options and 'help' come here + abort ("cli script must be one of #{CLI_Script_actions}, not [#{ARGV[1]}]") + end when 'rake' if File.exist?('Rakefile') From b057f162f43931fffab5f347cbb37da1b4046860 Mon Sep 17 00:00:00 2001 From: Joseph Brothers Date: Fri, 30 Aug 2024 14:13:35 -0600 Subject: [PATCH 07/24] incorporated review comments and added 'openc3cli script list / ' feature --- openc3/bin/openc3cli | 113 ++++++++++++++++++++++++++++--------------- 1 file changed, 73 insertions(+), 40 deletions(-) diff --git a/openc3/bin/openc3cli b/openc3/bin/openc3cli index 9ebb83717..dcfb41cc8 100755 --- a/openc3/bin/openc3cli +++ b/openc3/bin/openc3cli @@ -59,8 +59,7 @@ MIGRATE_PARSER = OptionParser.new do |opts| end ERROR_CODE = 1 -CLI_Script_actions = %w(help list run wait) -Dash_num_pattern=/^--?(\d+)/ # one or two dashes followed by at least one digit, capturing the digits +CLI_SCRIPT_ACTION = %w(help list run spawn) $script_left_waiting = '' trap('INT') do abort("Interrupted at console; exiting.#{$script_left_waiting}") @@ -72,12 +71,13 @@ def print_usage puts " cli help # Displays this information" puts " cli rake # Runs rake in the local directory" puts " cli irb # Runs irb in the local directory" - puts " cli script run NAME SCOPE variable1=value1 variable2=value2 # Starts named script" - puts " cli script wait NAME SCOPE variable1=value1 variable2=value2 # Starts named script showing status on console,\ - default until error or exit" + puts " cli script list /PATH SCOPE # lists script names filtered by path within scope, 'DEFAULT' if not given" + puts " cli script spawn NAME SCOPE variable1=value1 variable2=value2 # Starts named script remotely" + puts " cli script run NAME SCOPE variable1=value1 variable2=value2 # Starts named script, monitoring status on console,\ + by default until error or exit" puts " PARAMETERS name-value pairs to form the script's runtime environment" - puts " OPTIONS: --MAX_SECONDS to show status before detaching from the running script; ie --100" - puts " --disconnect will run the script without connecting" + puts " OPTIONS: --wait 0 seconds to monitor status before detaching from the running script; ie --wait 100" + puts " --disconnect run the script without connecting" puts " cli validate /PATH/FILENAME.gem SCOPE variables.txt # Validate a COSMOS plugin gem file" puts " cli load /PATH/FILENAME.gem SCOPE variables.txt # Loads a COSMOS plugin gem file" puts " cli list # Lists installed plugins, SCOPE is DEFAULT if not given" @@ -656,7 +656,8 @@ def run_bridge(filename, params) end end -def await_dotter(script_id) +def script_monitor(script_id) + ret_code = ERROR_CODE OpenC3::RunningScriptWebSocketApi.new(id: script_id) do |api| while (resp = api.read) do # see ScriptRunner.vue for types and states @@ -690,22 +691,45 @@ def await_dotter(script_id) when 'complete' $script_left_waiting = '' puts 'script complete' + ret_code = 0 break else puts resp.pretty_inspect end end end + return ret_code end -def cli_script_await(disconnect=false, environment={}, args=[]) +def cli_script_list(args=[]) + path = '' + if (args[0] && args[0][0] == '/') + path = (args.shift)[1..-1]+'/' + end + scope = args[0] + scope ||= 'DEFAULT' + script_list(scope: scope).each do |script_name| + puts (script_name) if script_name.start_with?(path) + end + return 0 +end + +def cli_script_run(disconnect=false, environment={}, args=[]) # we are limiting the wait for status, not the script run time # we make 0 mean 'forever' + ret_code = ERROR_CODE wait_limit = 0 - if (match = args[0].match(Dash_num_pattern)) - wait_limit = match.captures[0].to_i - puts "Will use MAX_SECONDS [#{match.captures[0]}]" - args.shift + if (i = args.index('--wait')) + begin + args.delete('--wait') + # pull out the flag + seconds = args[i] + wait_limit = Integer(seconds, 10) # only decimal, ignore leading 0 + args.delete_at(i) + # and its value + rescue ArgumentError + abort(" --wait requires a number of seconds to wait, not [#{seconds}]") + end end abort("No script file provided") if args[0].nil? scope = args[1] @@ -713,48 +737,49 @@ def cli_script_await(disconnect=false, environment={}, args=[]) id = script_run(args[0], disconnect: disconnect, environment: environment, scope: scope) # could raise $script_left_waiting = " Script #{args[0]} still running remotely.\n" # for Ctrl-C if (wait_limit < 1) then - await_dotter(id) + ret_code = script_monitor(id) else - Timeout::timeout(wait_limit, nil, "MAX_SECONDS #{wait_limit} exceeded") do - await_dotter(id) + Timeout::timeout(wait_limit, nil, "--wait #{wait_limit} exceeded") do + script_monitor(id) rescue Timeout::ExitException, Timeout::Error => tmoexcp # Timeout exceptions are also thrown by the Websocket API, so we check - if tmoexcp.message =~ /^MAX_SECONDS / + if tmoexcp.message =~ /^--wait / puts tmoexcp.message+", detaching from running script #{args[0]}" else raise end end end + return ret_code end -def cli_script_run(disconnect=false, environment={}, args=[]) - if (match = args[0].match(Dash_num_pattern)) # -11, --11, and 11 are probably not a script file name - wrong_wait = match.captures[0] - wrong_wait ||= '0' - abort("Did you mean \"script wait --#{wrong_wait.to_i} #{args[1]}\"?") +def cli_script_spawn(disconnect=false, environment={}, args=[]) +ret_code = ERROR_CODE + if (args.index('--wait')) + abort("Did you mean \"script run --wait [...]\"?") end abort("No script file provided") if args[0].nil? # heaven help you if you left out the script name scope = args[1] scope ||= 'DEFAULT' if (id = script_run(args[0], disconnect: disconnect, environment: environment, scope: scope)) - puts "Script #{args[0]} started remotely." - await_dotter(id) + puts "Script #{args[0]} started remotely." + ret_code = script_monitor(id) else puts "\nFailed to start #{args[0]}" end + return ret_code end -## cli_script(args) turns an ARGV of [run|wait] < --123... > < --disconnect > SCRIPT -# into function calls and tidied parameters to remote-control RunningScriptWebSocketApi +## cli_script(args) turns an ARGV of [spawn|run] <--wait 123...> <--disconnect> SCRIPT +# into function calls and tidied parameters to remote-control a script via RunningScriptWebSocketApi def cli_script(args=[]) + ret_code = ERROR_CODE # handled lower down #scope ||= 'DEFAULT' check_environment() - command = args[0] - args.shift - # peel off the command + command = args.shift + discon = args.delete('--disconnect'){true} discon ||= false # pull out the disconnect flag @@ -768,17 +793,26 @@ def cli_script(args=[]) end end case command - # args[] should now be ["--100", "script_name", ""] - # or ["--100", "script_name"] + # for list + # args[] should now be ["/", ""] + # or ["/"] + # or [""] + # or [] + when 'list' + ret_code = cli_script_list(args) + # for spawn or run + # args[] should now be ["--wait 100", "script_name", ""] + # or ["--wait 100", "script_name"] # or ["script_name", ""] # or ["script_name"] + when 'spawn' + ret_code = cli_script_spawn(discon, environ, args) when 'run' - cli_script_run(discon, environ, args) - when 'wait' - cli_script_wait(discon, environ, args) + ret_code = cli_script_run(discon, environ, args) else - abort 'openc3cli internall error: parsing arguments' + abort 'openc3cli internal error: parsing arguments' end + return ret_code end if not ARGV[0].nil? # argument(s) given @@ -791,16 +825,15 @@ if not ARGV[0].nil? # argument(s) given IRB.start when 'script' + ret_code = ERROR_CODE case ARGV[1] - when 'list' - # list, list running, and list completed are future features - abort ("[#{ARGV[1]}] not implemented") - when 'run', 'wait' - cli_script(ARGV[1..-1]) + when 'list', 'run', 'spawn' + ret_code = cli_script(ARGV[1..-1]) else # invalid actions, misplaced and malformed leading options and 'help' come here abort ("cli script must be one of #{CLI_Script_actions}, not [#{ARGV[1]}]") end + ret_code when 'rake' if File.exist?('Rakefile') From 197e761de32f2d7badad5e83c71feae16c31e906 Mon Sep 17 00:00:00 2001 From: Joseph Brothers Date: Fri, 30 Aug 2024 15:54:28 -0600 Subject: [PATCH 08/24] add another raise-on-empty-body to running_scripts and two tests for cli script in CI --- .github/workflows/cli.yml | 6 ++++++ .../app/models/running_script.rb | 1 + 2 files changed, 7 insertions(+) diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index 75bb4e82f..ba17a1b53 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -101,6 +101,12 @@ jobs: ls targets/MY_CLI/lib | grep example_limits_response.rb ../openc3.sh cliroot rake build VERSION=1.0.3 ../openc3.sh cliroot validate openc3-cosmos-cli-test-1.0.3.gem + - name: openc3.sh cli script list, run + shell: 'script -q -e -c "bash {0}"' + run: | + set -e + ./openc3.sh cliroot script list | grep "INST2" || true + ./openc3.sh cliroot script run --wait 10 INST/procedures/checks.rb | grep "script complete" || true - name: openc3.sh util save,load shell: 'script -q -e -c "bash {0}"' run: | diff --git a/openc3-cosmos-script-runner-api/app/models/running_script.rb b/openc3-cosmos-script-runner-api/app/models/running_script.rb index 0b5e047b9..83c1a68b5 100644 --- a/openc3-cosmos-script-runner-api/app/models/running_script.rb +++ b/openc3-cosmos-script-runner-api/app/models/running_script.rb @@ -1271,6 +1271,7 @@ def load_file_into_script(filename) OpenC3::Store.publish(["script-api", "running-script-channel:#{@id}"].compact.join(":"), JSON.generate({ type: :file, filename: filename, text: @body.to_utf8, breakpoints: breakpoints })) else text = ::Script.body(@scope, filename) + raise "nothing runnable in scope: [#{@scope} for name: [#{filename}]]" if text.nil? @@file_cache[filename] = text @body = text OpenC3::Store.publish(["script-api", "running-script-channel:#{@id}"].compact.join(":"), JSON.generate({ type: :file, filename: filename, text: @body.to_utf8, breakpoints: breakpoints })) From 0a111e63675c3850c719be60ace3805f1719a522 Mon Sep 17 00:00:00 2001 From: Joseph Brothers Date: Fri, 30 Aug 2024 16:28:39 -0600 Subject: [PATCH 09/24] respond to review comments --- openc3-cosmos-script-runner-api/app/models/running_script.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openc3-cosmos-script-runner-api/app/models/running_script.rb b/openc3-cosmos-script-runner-api/app/models/running_script.rb index 83c1a68b5..ac06938a8 100644 --- a/openc3-cosmos-script-runner-api/app/models/running_script.rb +++ b/openc3-cosmos-script-runner-api/app/models/running_script.rb @@ -427,7 +427,7 @@ def initialize(id, scope, name, disconnect) # Retrieve file @body = ::Script.body(@scope, name) - raise "nothing runnable in scope: [#{@scope} for name: [#{name}]]" if @body.nil? + raise "Script not found: #{name}" if @body.nil? breakpoints = @@breakpoints[filename]&.filter { |_, present| present }&.map { |line_number, _| line_number - 1 } # -1 because frontend lines are 0-indexed breakpoints ||= [] OpenC3::Store.publish(["script-api", "running-script-channel:#{@id}"].compact.join(":"), @@ -1271,7 +1271,7 @@ def load_file_into_script(filename) OpenC3::Store.publish(["script-api", "running-script-channel:#{@id}"].compact.join(":"), JSON.generate({ type: :file, filename: filename, text: @body.to_utf8, breakpoints: breakpoints })) else text = ::Script.body(@scope, filename) - raise "nothing runnable in scope: [#{@scope} for name: [#{filename}]]" if text.nil? + raise "Script not found: #{name}" if text.nil? @@file_cache[filename] = text @body = text OpenC3::Store.publish(["script-api", "running-script-channel:#{@id}"].compact.join(":"), JSON.generate({ type: :file, filename: filename, text: @body.to_utf8, breakpoints: breakpoints })) From 29a837c7c17b7880c6c6a6a695df616a3b5908ef Mon Sep 17 00:00:00 2001 From: Joseph Brothers Date: Fri, 30 Aug 2024 16:29:19 -0600 Subject: [PATCH 10/24] respond to review comments --- openc3/bin/openc3cli | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/openc3/bin/openc3cli b/openc3/bin/openc3cli index dcfb41cc8..929be5e17 100755 --- a/openc3/bin/openc3cli +++ b/openc3/bin/openc3cli @@ -59,10 +59,10 @@ MIGRATE_PARSER = OptionParser.new do |opts| end ERROR_CODE = 1 -CLI_SCRIPT_ACTION = %w(help list run spawn) -$script_left_waiting = '' +CLI_SCRIPT_ACTIONS = %w(help list run spawn) +$script_interrupt_text = '' trap('INT') do - abort("Interrupted at console; exiting.#{$script_left_waiting}") + abort("Interrupted at console; exiting.#{$script_interrupt_text}") end # Prints the usage text for the openc3cli executable @@ -665,7 +665,7 @@ def script_monitor(script_id) when 'running', 'breakpoint', 'waiting' puts resp.pretty_inspect when 'error', 'fatal' - $script_left_waiting = '' + $script_interrupt_text = '' puts 'script failed' break when 'file' @@ -674,7 +674,7 @@ def script_monitor(script_id) fn = resp['filename'].nil? ? '' : resp['filename'] puts "At [#{fn}:#{resp['line_no']}] state [#{resp['state']}]" if resp['state'] == 'error' - $script_left_waiting = '' + $script_interrupt_text = '' puts 'script failed' break end @@ -682,14 +682,14 @@ def script_monitor(script_id) puts resp['line'] when 'paused' if resp['state'] == 'fatal' - $script_left_waiting = '' + $script_interrupt_text = '' puts 'script failed' break end when 'time' puts resp.pretty_inspect when 'complete' - $script_left_waiting = '' + $script_interrupt_text = '' puts 'script complete' ret_code = 0 break @@ -735,7 +735,7 @@ def cli_script_run(disconnect=false, environment={}, args=[]) scope = args[1] scope ||= 'DEFAULT' id = script_run(args[0], disconnect: disconnect, environment: environment, scope: scope) # could raise - $script_left_waiting = " Script #{args[0]} still running remotely.\n" # for Ctrl-C + $script_interrupt_text = " Script #{args[0]} still running remotely.\n" # for Ctrl-C if (wait_limit < 1) then ret_code = script_monitor(id) else @@ -775,7 +775,6 @@ end # into function calls and tidied parameters to remote-control a script via RunningScriptWebSocketApi def cli_script(args=[]) ret_code = ERROR_CODE - # handled lower down #scope ||= 'DEFAULT' check_environment() command = args.shift @@ -831,7 +830,7 @@ if not ARGV[0].nil? # argument(s) given ret_code = cli_script(ARGV[1..-1]) else # invalid actions, misplaced and malformed leading options and 'help' come here - abort ("cli script must be one of #{CLI_Script_actions}, not [#{ARGV[1]}]") + abort ("cli script must be one of #{CLI_SCRIPT_ACTIONS}, not [#{ARGV[1]}]") end ret_code @@ -987,3 +986,5 @@ if not ARGV[0].nil? # argument(s) given else # No arguments given print_usage() end + +exit(ret_code) From 4bde7d6935fcd3516f316ef032e290ca36b84d8c Mon Sep 17 00:00:00 2001 From: Joseph Brothers Date: Fri, 30 Aug 2024 17:33:56 -0600 Subject: [PATCH 11/24] responses to review comments --- openc3/bin/openc3cli | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/openc3/bin/openc3cli b/openc3/bin/openc3cli index 929be5e17..8d92b00b9 100755 --- a/openc3/bin/openc3cli +++ b/openc3/bin/openc3cli @@ -33,7 +33,6 @@ require 'openc3/models/gem_model' require 'openc3/models/migration_model' require 'openc3/models/tool_model' require 'openc3/packets/packet_config' -require 'openc3/script' require 'openc3/bridge/bridge' require 'ostruct' require 'optparse' @@ -404,6 +403,7 @@ def list_plugins(scope:) if $openc3_in_cluster names = OpenC3::PluginModel.names(scope: scope) else + require 'openc3/script' names = plugin_list(scope: scope) end names.each do |name| @@ -505,6 +505,7 @@ def load_plugin(plugin_file_path, scope:, plugin_hash_file: nil, force: false) end else # Outside Cluster + require 'openc3/script' if plugin_hash_file plugin_hash = JSON.parse(File.read(plugin_hash_file), :allow_nan => true, :create_additions => true) else @@ -532,6 +533,7 @@ def unload_plugin(plugin_name, scope:) end else # Outside Cluster + require 'openc3/script' process_name = plugin_uninstall(plugin_name, scope: scope) print "Uninstalling..." wait_process_complete(process_name) @@ -549,6 +551,7 @@ def cli_pkg_install(filename, scope:) end else # Outside Cluster + require 'openc3/script' process_name = package_install(filename, scope: scope) print "Installing..." wait_process_complete(process_name) @@ -566,6 +569,7 @@ def cli_pkg_uninstall(filename, scope:) end else # Outside Cluster + require 'openc3/script' process_name = package_uninstall(filename, scope: scope) if File.extname(filename) == '.rb' puts "Uninstalled" @@ -658,6 +662,7 @@ end def script_monitor(script_id) ret_code = ERROR_CODE + require 'openc3/script' OpenC3::RunningScriptWebSocketApi.new(id: script_id) do |api| while (resp = api.read) do # see ScriptRunner.vue for types and states @@ -708,6 +713,7 @@ def cli_script_list(args=[]) end scope = args[0] scope ||= 'DEFAULT' + require 'openc3/script' script_list(scope: scope).each do |script_name| puts (script_name) if script_name.start_with?(path) end @@ -734,13 +740,14 @@ def cli_script_run(disconnect=false, environment={}, args=[]) abort("No script file provided") if args[0].nil? scope = args[1] scope ||= 'DEFAULT' + require 'openc3/script' id = script_run(args[0], disconnect: disconnect, environment: environment, scope: scope) # could raise $script_interrupt_text = " Script #{args[0]} still running remotely.\n" # for Ctrl-C if (wait_limit < 1) then ret_code = script_monitor(id) else Timeout::timeout(wait_limit, nil, "--wait #{wait_limit} exceeded") do - script_monitor(id) + ret_code = script_monitor(id) rescue Timeout::ExitException, Timeout::Error => tmoexcp # Timeout exceptions are also thrown by the Websocket API, so we check if tmoexcp.message =~ /^--wait / @@ -754,7 +761,7 @@ def cli_script_run(disconnect=false, environment={}, args=[]) end def cli_script_spawn(disconnect=false, environment={}, args=[]) -ret_code = ERROR_CODE + ret_code = ERROR_CODE if (args.index('--wait')) abort("Did you mean \"script run --wait [...]\"?") end @@ -762,6 +769,7 @@ ret_code = ERROR_CODE # heaven help you if you left out the script name scope = args[1] scope ||= 'DEFAULT' + require 'openc3/script' if (id = script_run(args[0], disconnect: disconnect, environment: environment, scope: scope)) puts "Script #{args[0]} started remotely." ret_code = script_monitor(id) @@ -811,7 +819,7 @@ def cli_script(args=[]) else abort 'openc3cli internal error: parsing arguments' end - return ret_code + exit(ret_code) end if not ARGV[0].nil? # argument(s) given @@ -824,15 +832,13 @@ if not ARGV[0].nil? # argument(s) given IRB.start when 'script' - ret_code = ERROR_CODE case ARGV[1] when 'list', 'run', 'spawn' - ret_code = cli_script(ARGV[1..-1]) + cli_script(ARGV[1..-1]) else # invalid actions, misplaced and malformed leading options and 'help' come here abort ("cli script must be one of #{CLI_SCRIPT_ACTIONS}, not [#{ARGV[1]}]") end - ret_code when 'rake' if File.exist?('Rakefile') @@ -986,5 +992,3 @@ if not ARGV[0].nil? # argument(s) given else # No arguments given print_usage() end - -exit(ret_code) From 0a6fd7ddbe539cae5fb5a67c5057b0a3c8ab54ef Mon Sep 17 00:00:00 2001 From: Joseph Brothers Date: Tue, 3 Sep 2024 14:37:18 -0600 Subject: [PATCH 12/24] respond to review comments, fix bug in cli_script_spawn, fix tests and add one for spawn --- .github/workflows/cli.yml | 10 +++++++--- .../app/models/running_script.rb | 2 +- openc3/bin/openc3cli | 8 +++----- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index ba17a1b53..c1d105b70 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -101,12 +101,16 @@ jobs: ls targets/MY_CLI/lib | grep example_limits_response.rb ../openc3.sh cliroot rake build VERSION=1.0.3 ../openc3.sh cliroot validate openc3-cosmos-cli-test-1.0.3.gem - - name: openc3.sh cli script list, run + - name: openc3.sh cli script list, run, spawn shell: 'script -q -e -c "bash {0}"' run: | set -e - ./openc3.sh cliroot script list | grep "INST2" || true - ./openc3.sh cliroot script run --wait 10 INST/procedures/checks.rb | grep "script complete" || true + # list of script filenames on server contains a target folder name + ./openc3.sh cliroot script list | grep -q "INST2" + # spawning a script in a subfolder returns 0 and prints only a PID + ./openc3.sh cliroot script spawn INST/procedures/checks.rb | grep -q "^\s*\d+\s*$" + # running a known good script with a wait value returns 0 and prints a success message + ./openc3.sh cliroot script run --wait 10 INST/procedures/checks.rb | grep -q "script complete" - name: openc3.sh util save,load shell: 'script -q -e -c "bash {0}"' run: | diff --git a/openc3-cosmos-script-runner-api/app/models/running_script.rb b/openc3-cosmos-script-runner-api/app/models/running_script.rb index ac06938a8..60f1f55df 100644 --- a/openc3-cosmos-script-runner-api/app/models/running_script.rb +++ b/openc3-cosmos-script-runner-api/app/models/running_script.rb @@ -1271,7 +1271,7 @@ def load_file_into_script(filename) OpenC3::Store.publish(["script-api", "running-script-channel:#{@id}"].compact.join(":"), JSON.generate({ type: :file, filename: filename, text: @body.to_utf8, breakpoints: breakpoints })) else text = ::Script.body(@scope, filename) - raise "Script not found: #{name}" if text.nil? + raise "Script not found: #{filename}" if text.nil? @@file_cache[filename] = text @body = text OpenC3::Store.publish(["script-api", "running-script-channel:#{@id}"].compact.join(":"), JSON.generate({ type: :file, filename: filename, text: @body.to_utf8, breakpoints: breakpoints })) diff --git a/openc3/bin/openc3cli b/openc3/bin/openc3cli index 8d92b00b9..17be7fbb1 100755 --- a/openc3/bin/openc3cli +++ b/openc3/bin/openc3cli @@ -751,7 +751,7 @@ def cli_script_run(disconnect=false, environment={}, args=[]) rescue Timeout::ExitException, Timeout::Error => tmoexcp # Timeout exceptions are also thrown by the Websocket API, so we check if tmoexcp.message =~ /^--wait / - puts tmoexcp.message+", detaching from running script #{args[0]}" + puts tmoexcp.message + ", detaching from running script #{args[0]}" else raise end @@ -771,10 +771,8 @@ def cli_script_spawn(disconnect=false, environment={}, args=[]) scope ||= 'DEFAULT' require 'openc3/script' if (id = script_run(args[0], disconnect: disconnect, environment: environment, scope: scope)) - puts "Script #{args[0]} started remotely." - ret_code = script_monitor(id) - else - puts "\nFailed to start #{args[0]}" + puts id + ret_code = 0 end return ret_code end From 562433c8d02e8fa2f11990a087f77c4bab3693fd Mon Sep 17 00:00:00 2001 From: Joseph Brothers Date: Tue, 3 Sep 2024 16:53:15 -0600 Subject: [PATCH 13/24] fix newest test, rename a function and correct a comment --- .github/workflows/cli.yml | 2 +- openc3/bin/openc3cli | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index c1d105b70..bcc0a525c 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -108,7 +108,7 @@ jobs: # list of script filenames on server contains a target folder name ./openc3.sh cliroot script list | grep -q "INST2" # spawning a script in a subfolder returns 0 and prints only a PID - ./openc3.sh cliroot script spawn INST/procedures/checks.rb | grep -q "^\s*\d+\s*$" + ./openc3.sh cliroot script spawn INST/procedures/checks.rb | grep -qv "^\s*\d+\s*$" # running a known good script with a wait value returns 0 and prints a success message ./openc3.sh cliroot script run --wait 10 INST/procedures/checks.rb | grep -q "script complete" - name: openc3.sh util save,load diff --git a/openc3/bin/openc3cli b/openc3/bin/openc3cli index 17be7fbb1..952241032 100755 --- a/openc3/bin/openc3cli +++ b/openc3/bin/openc3cli @@ -660,7 +660,7 @@ def run_bridge(filename, params) end end -def script_monitor(script_id) +def cli_script_monitor(script_id) ret_code = ERROR_CODE require 'openc3/script' OpenC3::RunningScriptWebSocketApi.new(id: script_id) do |api| @@ -744,12 +744,12 @@ def cli_script_run(disconnect=false, environment={}, args=[]) id = script_run(args[0], disconnect: disconnect, environment: environment, scope: scope) # could raise $script_interrupt_text = " Script #{args[0]} still running remotely.\n" # for Ctrl-C if (wait_limit < 1) then - ret_code = script_monitor(id) + ret_code = cli_script_monitor(id) else Timeout::timeout(wait_limit, nil, "--wait #{wait_limit} exceeded") do - ret_code = script_monitor(id) + ret_code = cli_script_monitor(id) rescue Timeout::ExitException, Timeout::Error => tmoexcp - # Timeout exceptions are also thrown by the Websocket API, so we check + # Timeout exceptions are also raised by the Websocket API, so we check if tmoexcp.message =~ /^--wait / puts tmoexcp.message + ", detaching from running script #{args[0]}" else From 079e21a73ed1d3bc969447e34ed714f5c8d0384a Mon Sep 17 00:00:00 2001 From: Joseph Brothers Date: Tue, 3 Sep 2024 17:15:27 -0600 Subject: [PATCH 14/24] ran on my machine: this is for debugging in CI --- .github/workflows/cli.yml | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index bcc0a525c..75a2d8825 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -105,12 +105,15 @@ jobs: shell: 'script -q -e -c "bash {0}"' run: | set -e - # list of script filenames on server contains a target folder name - ./openc3.sh cliroot script list | grep -q "INST2" - # spawning a script in a subfolder returns 0 and prints only a PID - ./openc3.sh cliroot script spawn INST/procedures/checks.rb | grep -qv "^\s*\d+\s*$" - # running a known good script with a wait value returns 0 and prints a success message - ./openc3.sh cliroot script run --wait 10 INST/procedures/checks.rb | grep -q "script complete" + # listing script filenames on server also shows a target folder name + # orig #./openc3.sh cliroot script list | grep -q "INST2" + ./openc3.sh cliroot script list + # spawning a script in a subfolder prints only a PID + # orig #./openc3.sh cliroot script spawn INST/procedures/checks.rb | grep -qv "^\s*\d+\s*$" + ./openc3.sh cliroot script spawn INST/procedures/checks.rb + # running a known good script with a wait value longer than its run time prints a success message + # orig # ./openc3.sh cliroot script run --wait 10 INST/procedures/checks.rb | grep -q "script complete" + ./openc3.sh cliroot script run --wait 10 INST/procedures/checks.rb - name: openc3.sh util save,load shell: 'script -q -e -c "bash {0}"' run: | From b1b19ad64768c039897f3256722e368742a454ca Mon Sep 17 00:00:00 2001 From: Ryan Melton Date: Thu, 5 Sep 2024 15:20:53 -0600 Subject: [PATCH 15/24] Update openc3.sh to use docker compose run instead of docker run --- openc3.bat | 6 ++---- openc3.sh | 7 ++----- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/openc3.bat b/openc3.bat index 1c0d11ff9..a548d236a 100644 --- a/openc3.bat +++ b/openc3.bat @@ -14,16 +14,14 @@ if "%1" == "cli" ( REM mapped as volume (-v) /openc3/local and container working directory (-w) also set to /openc3/local. REM This allows tools running in the container to have a consistent path to the current working directory. REM Run the command "ruby /openc3/bin/openc3" with all parameters ignoring the first. - docker network create openc3-cosmos-network - docker run -it --rm --env-file %~dp0.env --network openc3-cosmos-network -v %cd%:/openc3/local -w /openc3/local !OPENC3_REGISTRY!/!OPENC3_NAMESPACE!/openc3-operator!OPENC3_IMAGE_SUFFIX!:!OPENC3_TAG! ruby /openc3/bin/openc3cli !params! + docker compose -f %~dp0compose.yaml run -it --rm -v %cd%:/openc3/local -w /openc3/local -e OPENC3_API_PASSWORD=!OPENC3_API_PASSWORD! --no-deps openc3-operator ruby /openc3/bin/openc3cli !params! GOTO :EOF ) if "%1" == "cliroot" ( FOR /F "tokens=*" %%i in ('findstr /V /B /L /C:# %~dp0.env') do SET %%i set params=%* call set params=%%params:*%1=%% - docker network create openc3-cosmos-network - docker run -it --rm --env-file %~dp0.env --user=root --network openc3-cosmos-network -v %cd%:/openc3/local -w /openc3/local !OPENC3_REGISTRY!/!OPENC3_NAMESPACE!/openc3-operator!OPENC3_IMAGE_SUFFIX!:!OPENC3_TAG! ruby /openc3/bin/openc3cli !params! + docker compose -f %~dp0compose.yaml run -it --rm --user=root -v %cd%:/openc3/local -w /openc3/local -e OPENC3_API_PASSWORD=!OPENC3_API_PASSWORD! --no-deps openc3-operator ruby /openc3/bin/openc3cli !params! GOTO :EOF ) if "%1" == "start" ( diff --git a/openc3.sh b/openc3.sh index 0a6edcd13..a6aa1a0b6 100755 --- a/openc3.sh +++ b/openc3.sh @@ -63,17 +63,14 @@ case $1 in # This allows tools running in the container to have a consistent path to the current working directory. # Run the command "ruby /openc3/bin/openc3cli" with all parameters starting at 2 since the first is 'openc3' args=`echo $@ | { read _ args; echo $args; }` - # Make sure the network exists - (docker network create openc3-cosmos-network || true) &> /dev/null - docker run -it --rm --env-file "$(dirname -- "$0")/.env" --user=$OPENC3_USER_ID:$OPENC3_GROUP_ID --network openc3-cosmos-network -v `pwd`:/openc3/local:z -w /openc3/local $OPENC3_REGISTRY/$OPENC3_NAMESPACE/openc3-operator$OPENC3_IMAGE_SUFFIX:$OPENC3_TAG ruby /openc3/bin/openc3cli $args + docker compose -f "$(dirname -- "$0")/compose.yaml" run -it --rm -v `pwd`:/openc3/local:z -w /openc3/local -e OPENC3_API_PASSWORD=$OPENC3_API_PASSWORD --no-deps openc3-operator ruby /openc3/bin/openc3cli $args set +a ;; cliroot ) set -a . "$(dirname -- "$0")/.env" args=`echo $@ | { read _ args; echo $args; }` - (docker network create openc3-cosmos-network || true) &> /dev/null - docker run -it --rm --env-file "$(dirname -- "$0")/.env" --user=root --network openc3-cosmos-network -v `pwd`:/openc3/local:z -w /openc3/local $OPENC3_REGISTRY/$OPENC3_NAMESPACE/openc3-operator$OPENC3_IMAGE_SUFFIX:$OPENC3_TAG ruby /openc3/bin/openc3cli $args + docker compose -f "$(dirname -- "$0")/compose.yaml" run -it --rm --user=root -v `pwd`:/openc3/local:z -w /openc3/local -e OPENC3_API_PASSWORD=$OPENC3_API_PASSWORD --no-deps openc3-operator ruby /openc3/bin/openc3cli $args set +a ;; start ) From c2054d08ff9203013b240c8e2acb47d93f1e7ee2 Mon Sep 17 00:00:00 2001 From: Ryan Melton Date: Thu, 5 Sep 2024 15:25:33 -0600 Subject: [PATCH 16/24] Update cli to use cmd-tlm-api container --- openc3.bat | 4 ++-- openc3.sh | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/openc3.bat b/openc3.bat index a548d236a..83f5d7bbc 100644 --- a/openc3.bat +++ b/openc3.bat @@ -14,14 +14,14 @@ if "%1" == "cli" ( REM mapped as volume (-v) /openc3/local and container working directory (-w) also set to /openc3/local. REM This allows tools running in the container to have a consistent path to the current working directory. REM Run the command "ruby /openc3/bin/openc3" with all parameters ignoring the first. - docker compose -f %~dp0compose.yaml run -it --rm -v %cd%:/openc3/local -w /openc3/local -e OPENC3_API_PASSWORD=!OPENC3_API_PASSWORD! --no-deps openc3-operator ruby /openc3/bin/openc3cli !params! + docker compose -f %~dp0compose.yaml run -it --rm -v %cd%:/openc3/local -w /openc3/local -e OPENC3_API_PASSWORD=!OPENC3_API_PASSWORD! --no-deps openc3-cosmos-cmd-tlm-api ruby /openc3/bin/openc3cli !params! GOTO :EOF ) if "%1" == "cliroot" ( FOR /F "tokens=*" %%i in ('findstr /V /B /L /C:# %~dp0.env') do SET %%i set params=%* call set params=%%params:*%1=%% - docker compose -f %~dp0compose.yaml run -it --rm --user=root -v %cd%:/openc3/local -w /openc3/local -e OPENC3_API_PASSWORD=!OPENC3_API_PASSWORD! --no-deps openc3-operator ruby /openc3/bin/openc3cli !params! + docker compose -f %~dp0compose.yaml run -it --rm --user=root -v %cd%:/openc3/local -w /openc3/local -e OPENC3_API_PASSWORD=!OPENC3_API_PASSWORD! --no-deps openc3-cosmos-cmd-tlm-api ruby /openc3/bin/openc3cli !params! GOTO :EOF ) if "%1" == "start" ( diff --git a/openc3.sh b/openc3.sh index a6aa1a0b6..2218ca3b8 100755 --- a/openc3.sh +++ b/openc3.sh @@ -63,14 +63,14 @@ case $1 in # This allows tools running in the container to have a consistent path to the current working directory. # Run the command "ruby /openc3/bin/openc3cli" with all parameters starting at 2 since the first is 'openc3' args=`echo $@ | { read _ args; echo $args; }` - docker compose -f "$(dirname -- "$0")/compose.yaml" run -it --rm -v `pwd`:/openc3/local:z -w /openc3/local -e OPENC3_API_PASSWORD=$OPENC3_API_PASSWORD --no-deps openc3-operator ruby /openc3/bin/openc3cli $args + docker compose -f "$(dirname -- "$0")/compose.yaml" run -it --rm -v `pwd`:/openc3/local:z -w /openc3/local -e OPENC3_API_PASSWORD=$OPENC3_API_PASSWORD --no-deps openc3-cosmos-cmd-tlm-api ruby /openc3/bin/openc3cli $args set +a ;; cliroot ) set -a . "$(dirname -- "$0")/.env" args=`echo $@ | { read _ args; echo $args; }` - docker compose -f "$(dirname -- "$0")/compose.yaml" run -it --rm --user=root -v `pwd`:/openc3/local:z -w /openc3/local -e OPENC3_API_PASSWORD=$OPENC3_API_PASSWORD --no-deps openc3-operator ruby /openc3/bin/openc3cli $args + docker compose -f "$(dirname -- "$0")/compose.yaml" run -it --rm --user=root -v `pwd`:/openc3/local:z -w /openc3/local -e OPENC3_API_PASSWORD=$OPENC3_API_PASSWORD --no-deps openc3-cosmos-cmd-tlm-api ruby /openc3/bin/openc3cli $args set +a ;; start ) From 1226b56e67b23cd9c2c3c81ecf888e27a15d64f5 Mon Sep 17 00:00:00 2001 From: Ryan Melton Date: Thu, 5 Sep 2024 17:33:39 -0600 Subject: [PATCH 17/24] Use detected docker compose --- openc3.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openc3.sh b/openc3.sh index 2218ca3b8..b69358bc3 100755 --- a/openc3.sh +++ b/openc3.sh @@ -63,14 +63,14 @@ case $1 in # This allows tools running in the container to have a consistent path to the current working directory. # Run the command "ruby /openc3/bin/openc3cli" with all parameters starting at 2 since the first is 'openc3' args=`echo $@ | { read _ args; echo $args; }` - docker compose -f "$(dirname -- "$0")/compose.yaml" run -it --rm -v `pwd`:/openc3/local:z -w /openc3/local -e OPENC3_API_PASSWORD=$OPENC3_API_PASSWORD --no-deps openc3-cosmos-cmd-tlm-api ruby /openc3/bin/openc3cli $args + ${DOCKER_COMPOSE_COMMAND} -f "$(dirname -- "$0")/compose.yaml" run -it --rm -v `pwd`:/openc3/local:z -w /openc3/local -e OPENC3_API_PASSWORD=$OPENC3_API_PASSWORD --no-deps openc3-cosmos-cmd-tlm-api ruby /openc3/bin/openc3cli $args set +a ;; cliroot ) set -a . "$(dirname -- "$0")/.env" args=`echo $@ | { read _ args; echo $args; }` - docker compose -f "$(dirname -- "$0")/compose.yaml" run -it --rm --user=root -v `pwd`:/openc3/local:z -w /openc3/local -e OPENC3_API_PASSWORD=$OPENC3_API_PASSWORD --no-deps openc3-cosmos-cmd-tlm-api ruby /openc3/bin/openc3cli $args + ${DOCKER_COMPOSE_COMMAND} -f "$(dirname -- "$0")/compose.yaml" run -it --rm --user=root -v `pwd`:/openc3/local:z -w /openc3/local -e OPENC3_API_PASSWORD=$OPENC3_API_PASSWORD --no-deps openc3-cosmos-cmd-tlm-api ruby /openc3/bin/openc3cli $args set +a ;; start ) From bc3f405c73c2a2dbfa5b6a8209a12d1ea5aba26e Mon Sep 17 00:00:00 2001 From: Joseph Brothers Date: Fri, 6 Sep 2024 13:20:13 -0600 Subject: [PATCH 18/24] fix cli script run/spawn --disconnect flag --- openc3/bin/openc3cli | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/openc3/bin/openc3cli b/openc3/bin/openc3cli index 952241032..77c8ffe3e 100755 --- a/openc3/bin/openc3cli +++ b/openc3/bin/openc3cli @@ -76,7 +76,7 @@ def print_usage by default until error or exit" puts " PARAMETERS name-value pairs to form the script's runtime environment" puts " OPTIONS: --wait 0 seconds to monitor status before detaching from the running script; ie --wait 100" - puts " --disconnect run the script without connecting" + puts " --disconnect run the script in disconnect mode" puts " cli validate /PATH/FILENAME.gem SCOPE variables.txt # Validate a COSMOS plugin gem file" puts " cli load /PATH/FILENAME.gem SCOPE variables.txt # Loads a COSMOS plugin gem file" puts " cli list # Lists installed plugins, SCOPE is DEFAULT if not given" @@ -782,19 +782,17 @@ end def cli_script(args=[]) ret_code = ERROR_CODE check_environment() - command = args.shift - - discon = args.delete('--disconnect'){true} - discon ||= false # pull out the disconnect flag + discon = args.delete('--disconnect') + discon = (discon.is_a? String) ? true : false environ = {} args.each do |arg| name, value = arg.split('=') if name and value + # add env[k]=v ; pull out "k=v" environ[name] = value args.delete(arg) - # add env[k]=v ; pull out "k=v" end end case command From eab95a5e36789a021a44c993d60c2d38bf45c92a Mon Sep 17 00:00:00 2001 From: Joseph Brothers Date: Fri, 6 Sep 2024 13:27:55 -0600 Subject: [PATCH 19/24] run the test script disconnected from the target --- .github/workflows/cli.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index 75a2d8825..776b0a727 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -112,7 +112,7 @@ jobs: # orig #./openc3.sh cliroot script spawn INST/procedures/checks.rb | grep -qv "^\s*\d+\s*$" ./openc3.sh cliroot script spawn INST/procedures/checks.rb # running a known good script with a wait value longer than its run time prints a success message - # orig # ./openc3.sh cliroot script run --wait 10 INST/procedures/checks.rb | grep -q "script complete" + # orig # ./openc3.sh cliroot script run --wait 10 --disconnect INST/procedures/checks.rb | grep -q "script complete" ./openc3.sh cliroot script run --wait 10 INST/procedures/checks.rb - name: openc3.sh util save,load shell: 'script -q -e -c "bash {0}"' From cdeb9f130b000c76878ae7548598983979f48d59 Mon Sep 17 00:00:00 2001 From: Joseph Brothers Date: Fri, 6 Sep 2024 14:00:06 -0600 Subject: [PATCH 20/24] set api password env var for cli test --- .github/workflows/cli.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index 776b0a727..a19e6f227 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -105,6 +105,7 @@ jobs: shell: 'script -q -e -c "bash {0}"' run: | set -e + echo "OPENC3_API_PASSWORD=password" >> "$GITHUB_ENV" # listing script filenames on server also shows a target folder name # orig #./openc3.sh cliroot script list | grep -q "INST2" ./openc3.sh cliroot script list From f0015b95bb97c30a254ebb0f710e49593e21b449 Mon Sep 17 00:00:00 2001 From: Joseph Brothers Date: Fri, 6 Sep 2024 15:11:08 -0600 Subject: [PATCH 21/24] push password hash to redis, matching env var --- .github/workflows/cli.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index a19e6f227..2753f0f85 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -106,6 +106,7 @@ jobs: run: | set -e echo "OPENC3_API_PASSWORD=password" >> "$GITHUB_ENV" + docker exec -it cosmos-openc3-redis-1 sh -c "echo -e 'AUTH openc3 openc3password\nset OPENC3__TOKEN 5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8' | redis-cli" # listing script filenames on server also shows a target folder name # orig #./openc3.sh cliroot script list | grep -q "INST2" ./openc3.sh cliroot script list From fd7784575f714c08a6d674aeab65488eb9843218 Mon Sep 17 00:00:00 2001 From: Jason Thomas Date: Fri, 13 Sep 2024 15:05:13 -0600 Subject: [PATCH 22/24] Add password to env --- .github/workflows/cli.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index 2753f0f85..7e7dbe4f5 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -12,6 +12,9 @@ on: pull_request: branches: [main] +env: + OPENC3_API_PASSWORD: password + jobs: openc3-cli: if: ${{ github.actor != 'dependabot[bot]' }} @@ -105,7 +108,6 @@ jobs: shell: 'script -q -e -c "bash {0}"' run: | set -e - echo "OPENC3_API_PASSWORD=password" >> "$GITHUB_ENV" docker exec -it cosmos-openc3-redis-1 sh -c "echo -e 'AUTH openc3 openc3password\nset OPENC3__TOKEN 5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8' | redis-cli" # listing script filenames on server also shows a target folder name # orig #./openc3.sh cliroot script list | grep -q "INST2" From 3a7e13ef306e7749c2214339af7314fdc783202c Mon Sep 17 00:00:00 2001 From: Jason Thomas Date: Fri, 13 Sep 2024 15:49:54 -0600 Subject: [PATCH 23/24] Rework script cli --- .github/workflows/cli.yml | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index 7e7dbe4f5..cb55e1c27 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -109,15 +109,14 @@ jobs: run: | set -e docker exec -it cosmos-openc3-redis-1 sh -c "echo -e 'AUTH openc3 openc3password\nset OPENC3__TOKEN 5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8' | redis-cli" - # listing script filenames on server also shows a target folder name - # orig #./openc3.sh cliroot script list | grep -q "INST2" - ./openc3.sh cliroot script list - # spawning a script in a subfolder prints only a PID - # orig #./openc3.sh cliroot script spawn INST/procedures/checks.rb | grep -qv "^\s*\d+\s*$" - ./openc3.sh cliroot script spawn INST/procedures/checks.rb - # running a known good script with a wait value longer than its run time prints a success message - # orig # ./openc3.sh cliroot script run --wait 10 --disconnect INST/procedures/checks.rb | grep -q "script complete" - ./openc3.sh cliroot script run --wait 10 INST/procedures/checks.rb + # list shows all the available file names + ./openc3.sh cliroot script list | tee /dev/tty | grep "INST/procedures/stash.rb" + # spawning a script prints only a PID + ./openc3.sh cliroot script spawn INST/procedures/checks.rb | grep -v "^\s*\d+\s*$" + # run a script that will fail and look for the failure message + ./openc3.sh cliroot script run --wait 10 INST/procedures/checks.rb | tee /dev/tty | grep -q "script failed" + # run a script that will complete successfully + ./openc3.sh cliroot script run INST/procedures/stash.rb | tee /dev/tty | grep "script complete" - name: openc3.sh util save,load shell: 'script -q -e -c "bash {0}"' run: | From 77316edb26627c5ea4a149187838fca754afdcf5 Mon Sep 17 00:00:00 2001 From: Jason Thomas Date: Fri, 13 Sep 2024 20:14:29 -0600 Subject: [PATCH 24/24] Check for empty password --- openc3/bin/openc3cli | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/openc3/bin/openc3cli b/openc3/bin/openc3cli index 77c8ffe3e..d0ab773a5 100755 --- a/openc3/bin/openc3cli +++ b/openc3/bin/openc3cli @@ -173,8 +173,8 @@ def migrate(args) end # Migrate cmd_tlm_server.txt info to plugin.txt - Dir.glob('targets/**/cmd_tlm_server*.txt') do |file| - File.open(file) do |file| + Dir.glob('targets/**/cmd_tlm_server*.txt') do |cmd_tlm_server_file| + File.open(cmd_tlm_server_file) do |file| file.each do |line| next if line =~ /^\s*#/ # Ignore comments next if line.strip.empty? # Ignore empty lines @@ -206,9 +206,9 @@ def migrate(args) lines = screen.split("\n") lines.map! do |line| if line.include?('Qt.') - line = "# FIXME (no Qt): #{line.sub("<%", "< %").sub("%>", "% >")}" + "# FIXME (no Qt): #{line.sub("<%", "< %").sub("%>", "% >")}" elsif line.include?('Cosmos::') - line = "# FIXME (no Cosmos::): #{line.sub("<%", "< %").sub("%>", "% >")}" + "# FIXME (no Cosmos::): #{line.sub("<%", "< %").sub("%>", "% >")}" else line end @@ -667,8 +667,6 @@ def cli_script_monitor(script_id) while (resp = api.read) do # see ScriptRunner.vue for types and states case resp['type'] - when 'running', 'breakpoint', 'waiting' - puts resp.pretty_inspect when 'error', 'fatal' $script_interrupt_text = '' puts 'script failed' @@ -691,13 +689,13 @@ def cli_script_monitor(script_id) puts 'script failed' break end - when 'time' - puts resp.pretty_inspect when 'complete' $script_interrupt_text = '' puts 'script complete' ret_code = 0 break + # These conditions are all handled by the else + # when 'running', 'breakpoint', 'waiting', 'time' else puts resp.pretty_inspect end @@ -711,11 +709,11 @@ def cli_script_list(args=[]) if (args[0] && args[0][0] == '/') path = (args.shift)[1..-1]+'/' end - scope = args[0] + scope = args[1] scope ||= 'DEFAULT' require 'openc3/script' script_list(scope: scope).each do |script_name| - puts (script_name) if script_name.start_with?(path) + puts(script_name) if script_name.start_with?(path) end return 0 end @@ -748,10 +746,10 @@ def cli_script_run(disconnect=false, environment={}, args=[]) else Timeout::timeout(wait_limit, nil, "--wait #{wait_limit} exceeded") do ret_code = cli_script_monitor(id) - rescue Timeout::ExitException, Timeout::Error => tmoexcp + rescue Timeout::ExitException, Timeout::Error => e # Timeout exceptions are also raised by the Websocket API, so we check - if tmoexcp.message =~ /^--wait / - puts tmoexcp.message + ", detaching from running script #{args[0]}" + if e.message =~ /^--wait / + puts e.message + ", detaching from running script #{args[0]}" else raise end @@ -782,6 +780,11 @@ end def cli_script(args=[]) ret_code = ERROR_CODE check_environment() + # Double check for the OPENC3_API_PASSWORD because it is absolutely required + # We always pass it via openc3.sh even if it's not defined so check for empty + if ENV['OPENC3_API_PASSWORD'].nil? or ENV['OPENC3_API_PASSWORD'].empty? + abort "OPENC3_API_PASSWORD environment variable is required for cli script" + end command = args.shift # pull out the disconnect flag discon = args.delete('--disconnect') @@ -833,7 +836,7 @@ if not ARGV[0].nil? # argument(s) given cli_script(ARGV[1..-1]) else # invalid actions, misplaced and malformed leading options and 'help' come here - abort ("cli script must be one of #{CLI_SCRIPT_ACTIONS}, not [#{ARGV[1]}]") + abort("cli script must be one of #{CLI_SCRIPT_ACTIONS}, not [#{ARGV[1]}]") end when 'rake'