Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cli script wait #1501

Merged
merged 26 commits into from
Sep 18, 2024
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
3d2c912
run script from command line and wait, monitoring status
JL-Brothers Aug 28, 2024
a20fac6
fix crash when seconds option provided
JL-Brothers Aug 28, 2024
fcff7aa
error exit if no script name provided
JL-Brothers Aug 28, 2024
79823d4
te was correct and meaningful, but CodeSpell, so tmo timeout excp exc…
JL-Brothers Aug 28, 2024
9ab7561
CodeSpell, timeout, and exit exception
JL-Brothers Aug 29, 2024
0bac9c8
more options and env parms for ARGV, and more message types and state…
JL-Brothers Aug 30, 2024
b057f16
incorporated review comments and added 'openc3cli script list /<path>…
JL-Brothers Aug 30, 2024
197e761
add another raise-on-empty-body to running_scripts and two tests for …
JL-Brothers Aug 30, 2024
0a111e6
respond to review comments
JL-Brothers Aug 30, 2024
29a837c
respond to review comments
JL-Brothers Aug 30, 2024
4bde7d6
responses to review comments
JL-Brothers Aug 30, 2024
0a6fd7d
respond to review comments, fix bug in cli_script_spawn, fix tests an…
JL-Brothers Sep 3, 2024
562433c
fix newest test, rename a function and correct a comment
JL-Brothers Sep 3, 2024
079e21a
ran on my machine: this is for debugging in CI
JL-Brothers Sep 3, 2024
b1b19ad
Update openc3.sh to use docker compose run instead of docker run
ryanmelt Sep 5, 2024
c2054d0
Update cli to use cmd-tlm-api container
ryanmelt Sep 5, 2024
1226b56
Use detected docker compose
ryanmelt Sep 5, 2024
bc3f405
fix cli script run/spawn --disconnect flag
JL-Brothers Sep 6, 2024
e798815
Merge branch 'cli_script_wait' of https://github.com/OpenC3/cosmos in…
JL-Brothers Sep 6, 2024
eab95a5
run the test script disconnected from the target
JL-Brothers Sep 6, 2024
94fa393
Merge branch 'main' into cli_script_wait
JL-Brothers Sep 6, 2024
cdeb9f1
set api password env var for cli test
JL-Brothers Sep 6, 2024
f0015b9
push password hash to redis, matching env var
JL-Brothers Sep 6, 2024
fd77845
Add password to env
jmthomas Sep 13, 2024
3a7e13e
Rework script cli
jmthomas Sep 13, 2024
77316ed
Check for empty password
jmthomas Sep 14, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .github/workflows/cli.yml
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,19 @@ 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, spawn
shell: 'script -q -e -c "bash {0}"'
run: |
set -e
# 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: |
Expand Down
2 changes: 2 additions & 0 deletions openc3-cosmos-script-runner-api/app/models/running_script.rb
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,7 @@ def initialize(id, scope, name, disconnect)

# Retrieve file
@body = ::Script.body(@scope, name)
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(":"),
Expand Down Expand Up @@ -1270,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 "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 }))
Expand Down
183 changes: 182 additions & 1 deletion openc3/bin/openc3cli
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,25 @@ MIGRATE_PARSER = OptionParser.new do |opts|
end
ERROR_CODE = 1

CLI_SCRIPT_ACTIONS = %w(help list run spawn)
$script_interrupt_text = ''
trap('INT') do
abort("Interrupted at console; exiting.#{$script_interrupt_text}")
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 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: --wait 0 seconds to monitor status before detaching from the running script; ie --wait 100"
JL-Brothers marked this conversation as resolved.
Show resolved Hide resolved
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 <SCOPE> # Lists installed plugins, SCOPE is DEFAULT if not given"
Expand Down Expand Up @@ -493,7 +506,6 @@ def load_plugin(plugin_file_path, scope:, plugin_hash_file: nil, force: false)
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
Expand Down Expand Up @@ -648,6 +660,166 @@ def run_bridge(filename, params)
end
end

def cli_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
case resp['type']
when 'running', 'breakpoint', 'waiting'
puts resp.pretty_inspect
when 'error', 'fatal'
$script_interrupt_text = ''
puts 'script failed'
break
when 'file'
puts "Filename #{resp['filename']} scope #{resp['scope']}"
when 'line'
fn = resp['filename'].nil? ? '<no file>' : resp['filename']
puts "At [#{fn}:#{resp['line_no']}] state [#{resp['state']}]"
if resp['state'] == 'error'
$script_interrupt_text = ''
puts 'script failed'
break
end
when 'output'
puts resp['line']
when 'paused'
if resp['state'] == 'fatal'
$script_interrupt_text = ''
puts 'script failed'
break
end
when 'time'
puts resp.pretty_inspect
when 'complete'
$script_interrupt_text = ''
puts 'script complete'
ret_code = 0
break
else
puts resp.pretty_inspect
end
end
end
return ret_code
end

def cli_script_list(args=[])
path = ''
if (args[0] && args[0][0] == '/')
path = (args.shift)[1..-1]+'/'
end
scope = args[0]
JL-Brothers marked this conversation as resolved.
Show resolved Hide resolved
scope ||= 'DEFAULT'
require 'openc3/script'
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 (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]
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 = cli_script_monitor(id)
else
Timeout::timeout(wait_limit, nil, "--wait #{wait_limit} exceeded") do
JL-Brothers marked this conversation as resolved.
Show resolved Hide resolved
ret_code = cli_script_monitor(id)
rescue Timeout::ExitException, Timeout::Error => tmoexcp
# 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
raise
end
end
end
return ret_code
end

def cli_script_spawn(disconnect=false, environment={}, args=[])
ret_code = ERROR_CODE
if (args.index('--wait'))
abort("Did you mean \"script run --wait <seconds> [...]\"?")
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'
require 'openc3/script'
if (id = script_run(args[0], disconnect: disconnect, environment: environment, scope: scope))
puts id
ret_code = 0
end
return ret_code
end

## cli_script(args) turns an ARGV of [spawn|run] <--wait 123...> <--disconnect> SCRIPT <scope> <ENV_A=1 ENV_B=2 ...>
# into function calls and tidied parameters to remote-control a script via RunningScriptWebSocketApi
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
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
# for list
# args[] should now be ["/<path>", "<scope>"]
# or ["/<path>"]
# or ["<scope>"]
# or []
when 'list'
ret_code = cli_script_list(args)
# for spawn or run
# args[] should now be ["--wait 100", "script_name", "<scope>"]
# or ["--wait 100", "script_name"]
# or ["script_name", "<scope>"]
# or ["script_name"]
when 'spawn'
ret_code = cli_script_spawn(discon, environ, args)
when 'run'
ret_code = cli_script_run(discon, environ, args)
else
abort 'openc3cli internal error: parsing arguments'
end
exit(ret_code)
end

if not ARGV[0].nil? # argument(s) given

# Handle each task
Expand All @@ -657,6 +829,15 @@ if not ARGV[0].nil? # argument(s) given
ARGV.clear
IRB.start

when 'script'
case ARGV[1]
when 'list', 'run', 'spawn'
cli_script(ARGV[1..-1])
else
# invalid actions, misplaced and malformed leading options and 'help' come here
abort ("cli script <action> must be one of #{CLI_SCRIPT_ACTIONS}, not [#{ARGV[1]}]")
end

when 'rake'
if File.exist?('Rakefile')
puts `rake #{ARGV[1..-1].join(' ')}`
Expand Down
Loading