openc3 7.0.0.pre.rc2 → 7.0.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/bin/openc3cli +13 -4
- data/bin/pipinstall +6 -7
- data/bin/pipuninstall +3 -5
- data/data/config/interface_modifiers.yaml +1 -1
- data/data/config/item_modifiers.yaml +18 -6
- data/data/config/telemetry.yaml +1 -1
- data/data/config/widgets.yaml +10 -0
- data/lib/openc3/accessors/json_accessor.rb +1 -1
- data/lib/openc3/api/cmd_api.rb +2 -0
- data/lib/openc3/api/settings_api.rb +2 -0
- data/lib/openc3/api/tlm_api.rb +3 -3
- data/lib/openc3/config/config_parser.rb +4 -4
- data/lib/openc3/conversions/conversion.rb +3 -3
- data/lib/openc3/core_ext/faraday.rb +4 -0
- data/lib/openc3/logs/log_writer.rb +24 -6
- data/lib/openc3/logs/packet_log_writer.rb +1 -4
- data/lib/openc3/logs/stream_log_pair.rb +11 -4
- data/lib/openc3/logs/text_log_writer.rb +1 -4
- data/lib/openc3/microservices/interface_microservice.rb +8 -2
- data/lib/openc3/microservices/log_microservice.rb +7 -2
- data/lib/openc3/microservices/microservice.rb +10 -4
- data/lib/openc3/microservices/queue_microservice.rb +9 -2
- data/lib/openc3/microservices/scope_cleanup_microservice.rb +116 -1
- data/lib/openc3/microservices/text_log_microservice.rb +4 -1
- data/lib/openc3/migrations/20241208080000_no_critical_cmd.rb +1 -1
- data/lib/openc3/migrations/20250402000000_periodic_only_default.rb +1 -1
- data/lib/openc3/migrations/20260203000000_remove_store_id.rb +28 -0
- data/lib/openc3/migrations/20260204000000_remove_decom_reducer.rb +29 -1
- data/lib/openc3/models/activity_model.rb +41 -9
- data/lib/openc3/models/auth_model.rb +54 -19
- data/lib/openc3/models/cvt_model.rb +2 -265
- data/lib/openc3/models/model.rb +16 -0
- data/lib/openc3/models/plugin_model.rb +18 -12
- data/lib/openc3/models/plugin_store_model.rb +1 -1
- data/lib/openc3/models/python_package_model.rb +2 -2
- data/lib/openc3/models/queue_model.rb +5 -3
- data/lib/openc3/models/script_engine_model.rb +1 -1
- data/lib/openc3/models/target_model.rb +75 -42
- data/lib/openc3/models/tool_config_model.rb +12 -0
- data/lib/openc3/models/tool_model.rb +18 -5
- data/lib/openc3/models/trigger_model.rb +1 -1
- data/lib/openc3/models/widget_model.rb +2 -9
- data/lib/openc3/operators/operator.rb +9 -7
- data/lib/openc3/packets/json_packet.rb +2 -0
- data/lib/openc3/packets/packet.rb +1 -0
- data/lib/openc3/packets/packet_config.rb +28 -12
- data/lib/openc3/script/calendar.rb +8 -0
- data/lib/openc3/script/script.rb +19 -0
- data/lib/openc3/script/storage.rb +6 -6
- data/lib/openc3/script/web_socket_api.rb +1 -1
- data/lib/openc3/system/system.rb +6 -6
- data/lib/openc3/tools/cmd_tlm_server/interface_thread.rb +0 -2
- data/lib/openc3/top_level.rb +15 -63
- data/lib/openc3/topics/command_topic.rb +1 -0
- data/lib/openc3/topics/limits_event_topic.rb +1 -1
- data/lib/openc3/utilities/authentication.rb +46 -7
- data/lib/openc3/utilities/authorization.rb +8 -1
- data/lib/openc3/utilities/aws_bucket.rb +2 -3
- data/lib/openc3/utilities/bucket_utilities.rb +3 -1
- data/lib/openc3/utilities/cli_generator.rb +7 -0
- data/lib/openc3/utilities/cmd_log.rb +1 -1
- data/lib/openc3/utilities/local_mode.rb +3 -0
- data/lib/openc3/utilities/process_manager.rb +1 -1
- data/lib/openc3/utilities/python_proxy.rb +11 -4
- data/lib/openc3/utilities/questdb_client.rb +764 -2
- data/lib/openc3/utilities/running_script.rb +25 -7
- data/lib/openc3/utilities/script.rb +452 -0
- data/lib/openc3/utilities/secrets.rb +1 -1
- data/lib/openc3/version.rb +5 -5
- data/templates/conversion/conversion.py +0 -8
- data/templates/conversion/conversion.rb +0 -11
- data/templates/tool_angular/package.json +2 -2
- data/templates/tool_react/package.json +1 -1
- data/templates/tool_svelte/package.json +1 -1
- data/templates/tool_vue/package.json +3 -3
- data/templates/widget/package.json +2 -2
- metadata +19 -19
- data/lib/openc3/migrations/20251022000000_remove_unique_id.rb +0 -23
- data/lib/openc3/migrations/20251213120000_reinstall_plugins.rb +0 -45
|
@@ -19,6 +19,11 @@ require 'openc3/utilities/local_mode'
|
|
|
19
19
|
|
|
20
20
|
module OpenC3
|
|
21
21
|
class ToolConfigModel
|
|
22
|
+
class InvalidNameError < StandardError; end
|
|
23
|
+
|
|
24
|
+
# Allowlist: letters, digits, hyphens, underscores, spaces, and periods
|
|
25
|
+
VALID_NAME_PATTERN = /\A[A-Za-z0-9_\-. ]+\z/
|
|
26
|
+
|
|
22
27
|
def self.config_tool_names(scope: $openc3_scope)
|
|
23
28
|
_, keys = Store.scan(0, match: "#{scope}__config__*", type: 'hash', count: 100)
|
|
24
29
|
# Just return the tool name that is used in the other APIs
|
|
@@ -26,19 +31,26 @@ module OpenC3
|
|
|
26
31
|
end
|
|
27
32
|
|
|
28
33
|
def self.list_configs(tool, scope: $openc3_scope)
|
|
34
|
+
raise InvalidNameError, "Invalid tool name: #{tool}" unless tool.match?(VALID_NAME_PATTERN)
|
|
29
35
|
Store.hkeys("#{scope}__config__#{tool}")
|
|
30
36
|
end
|
|
31
37
|
|
|
32
38
|
def self.load_config(tool, name, scope: $openc3_scope)
|
|
39
|
+
raise InvalidNameError, "Invalid tool name: #{tool}" unless tool.match?(VALID_NAME_PATTERN)
|
|
40
|
+
raise InvalidNameError, "Invalid config name: #{name}" unless name.match?(VALID_NAME_PATTERN)
|
|
33
41
|
Store.hget("#{scope}__config__#{tool}", name)
|
|
34
42
|
end
|
|
35
43
|
|
|
36
44
|
def self.save_config(tool, name, data, local_mode: true, scope: $openc3_scope)
|
|
45
|
+
raise InvalidNameError, "Invalid tool name: #{tool}" unless tool.match?(VALID_NAME_PATTERN)
|
|
46
|
+
raise InvalidNameError, "Invalid config name: #{name}" unless name.match?(VALID_NAME_PATTERN)
|
|
37
47
|
Store.hset("#{scope}__config__#{tool}", name, data)
|
|
38
48
|
LocalMode.save_tool_config(scope, tool, name, data) if local_mode
|
|
39
49
|
end
|
|
40
50
|
|
|
41
51
|
def self.delete_config(tool, name, local_mode: true, scope: $openc3_scope)
|
|
52
|
+
raise InvalidNameError, "Invalid tool name: #{tool}" unless tool.match?(VALID_NAME_PATTERN)
|
|
53
|
+
raise InvalidNameError, "Invalid config name: #{name}" unless name.match?(VALID_NAME_PATTERN)
|
|
42
54
|
Store.hdel("#{scope}__config__#{tool}", name)
|
|
43
55
|
LocalMode.delete_tool_config(scope, tool, name) if local_mode
|
|
44
56
|
end
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
# if purchased from OpenC3, Inc.
|
|
17
17
|
|
|
18
18
|
require 'openc3/models/model'
|
|
19
|
-
require 'openc3/models/scope_model'
|
|
19
|
+
# require 'openc3/models/scope_model' # Circular require
|
|
20
20
|
require 'openc3/utilities/bucket'
|
|
21
21
|
require 'openc3/utilities/bucket_utilities'
|
|
22
22
|
require 'rack'
|
|
@@ -179,7 +179,7 @@ module OpenC3
|
|
|
179
179
|
end
|
|
180
180
|
end
|
|
181
181
|
|
|
182
|
-
if @url and !@url.start_with?('/') and @url !~ URI::
|
|
182
|
+
if @url and !@url.start_with?('/') and @url !~ URI::RFC2396_PARSER.make_regexp
|
|
183
183
|
raise "URL must be a full URL (http://domain.com/path) or a relative path (/path)"
|
|
184
184
|
end
|
|
185
185
|
|
|
@@ -250,9 +250,22 @@ module OpenC3
|
|
|
250
250
|
|
|
251
251
|
variables["tool_name"] = @name
|
|
252
252
|
start_path = "/tools/#{@folder_name}/"
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
253
|
+
# Sort files so dependencies are uploaded before dependents:
|
|
254
|
+
# fonts first, then CSS, then index.html last (it triggers all other loads)
|
|
255
|
+
filenames = Dir.glob(gem_path + start_path + "**/*")
|
|
256
|
+
filenames.reject! { |f| f == '.' or f == '..' or File.directory?(f) }
|
|
257
|
+
filenames.sort_by! do |filename|
|
|
258
|
+
if filename.include?('/fonts/')
|
|
259
|
+
[0, filename]
|
|
260
|
+
elsif filename.include?('/css/')
|
|
261
|
+
[1, filename]
|
|
262
|
+
elsif File.basename(filename) == 'index.html'
|
|
263
|
+
[3, filename]
|
|
264
|
+
else
|
|
265
|
+
[2, filename]
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
filenames.each do |filename|
|
|
256
269
|
key = filename.split(gem_path + '/tools/')[-1]
|
|
257
270
|
extension = filename.split('.')[-1]
|
|
258
271
|
content_type = Rack::Mime.mime_type(".#{extension}")
|
|
@@ -19,7 +19,7 @@ require 'openc3/models/model'
|
|
|
19
19
|
require 'openc3/models/microservice_model'
|
|
20
20
|
require 'openc3/models/target_model'
|
|
21
21
|
require 'openc3/models/trigger_group_model'
|
|
22
|
-
require 'openc3/models/reaction_model'
|
|
22
|
+
# require 'openc3/models/reaction_model' # Remove circular require
|
|
23
23
|
require 'openc3/topics/autonomic_topic'
|
|
24
24
|
|
|
25
25
|
module OpenC3
|
|
@@ -15,9 +15,8 @@
|
|
|
15
15
|
# This file may also be used under the terms of a commercial license
|
|
16
16
|
# if purchased from OpenC3, Inc.
|
|
17
17
|
|
|
18
|
-
require 'openc3/top_level'
|
|
19
18
|
require 'openc3/models/model'
|
|
20
|
-
require 'openc3/models/scope_model'
|
|
19
|
+
# require 'openc3/models/scope_model' # Circular require
|
|
21
20
|
require 'openc3/utilities/bucket'
|
|
22
21
|
require 'openc3/utilities/bucket_utilities'
|
|
23
22
|
|
|
@@ -126,13 +125,6 @@ module OpenC3
|
|
|
126
125
|
end
|
|
127
126
|
|
|
128
127
|
def deploy(gem_path, variables, validate_only: false)
|
|
129
|
-
# Ensure tools bucket exists
|
|
130
|
-
bucket = nil
|
|
131
|
-
unless validate_only
|
|
132
|
-
bucket = Bucket.getClient()
|
|
133
|
-
bucket.ensure_public(ENV['OPENC3_TOOLS_BUCKET'])
|
|
134
|
-
end
|
|
135
|
-
|
|
136
128
|
filename = gem_path + "/tools/widgets/" + @full_name + '/' + @filename
|
|
137
129
|
|
|
138
130
|
# Load widget file
|
|
@@ -146,6 +138,7 @@ module OpenC3
|
|
|
146
138
|
unless validate_only
|
|
147
139
|
cache_control = BucketUtilities.get_cache_control(@filename)
|
|
148
140
|
# TODO: support widgets that aren't just a single js file (and its associated map file)
|
|
141
|
+
bucket = Bucket.getClient()
|
|
149
142
|
bucket.put_object(bucket: ENV['OPENC3_TOOLS_BUCKET'], content_type: 'application/javascript', cache_control: cache_control, key: @bucket_key, body: data)
|
|
150
143
|
data = File.read(filename + '.map', mode: "rb")
|
|
151
144
|
bucket.put_object(bucket: ENV['OPENC3_TOOLS_BUCKET'], content_type: 'application/json', cache_control: cache_control, key: @bucket_key + '.map', body: data)
|
|
@@ -54,8 +54,10 @@ module OpenC3
|
|
|
54
54
|
end
|
|
55
55
|
|
|
56
56
|
def finalize
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
unless closed?
|
|
58
|
+
extract()
|
|
59
|
+
close()
|
|
60
|
+
end
|
|
59
61
|
unlink()
|
|
60
62
|
|
|
61
63
|
output = ''
|
|
@@ -174,7 +176,7 @@ module OpenC3
|
|
|
174
176
|
end
|
|
175
177
|
@process.stop
|
|
176
178
|
end
|
|
177
|
-
FileUtils.remove_entry_secure(@temp_dir, true)
|
|
179
|
+
FileUtils.remove_entry_secure(@temp_dir, true) if @temp_dir
|
|
178
180
|
@process = nil
|
|
179
181
|
end
|
|
180
182
|
|
|
@@ -300,6 +302,7 @@ module OpenC3
|
|
|
300
302
|
# Respawn process
|
|
301
303
|
output = p.extract_output
|
|
302
304
|
Logger.error("Unexpected process died... respawning! #{p.cmd_line}\n#{output}\n", scope: p.scope)
|
|
305
|
+
p.hard_stop
|
|
303
306
|
p.start
|
|
304
307
|
end
|
|
305
308
|
end
|
|
@@ -308,6 +311,7 @@ module OpenC3
|
|
|
308
311
|
|
|
309
312
|
def shutdown_processes(processes)
|
|
310
313
|
# Make a copy so we don't mutate original
|
|
314
|
+
hard_stop_processes = processes.dup
|
|
311
315
|
processes = processes.dup
|
|
312
316
|
|
|
313
317
|
Logger.info("Commanding soft stops...")
|
|
@@ -331,10 +335,8 @@ module OpenC3
|
|
|
331
335
|
end
|
|
332
336
|
sleep(0.1)
|
|
333
337
|
end
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
processes.each { |_name, p| p.hard_stop }
|
|
337
|
-
end
|
|
338
|
+
Logger.debug("Commanding hard stops...")
|
|
339
|
+
hard_stop_processes.each { |_name, p| p.output_increment; p.extract_output; p.hard_stop }
|
|
338
340
|
end
|
|
339
341
|
|
|
340
342
|
def shutdown
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
# if purchased from OpenC3, Inc.
|
|
17
17
|
|
|
18
18
|
require 'digest'
|
|
19
|
+
require 'active_support/core_ext/object/deep_dup'
|
|
19
20
|
require 'openc3/packets/structure'
|
|
20
21
|
require 'openc3/packets/packet_item'
|
|
21
22
|
require 'openc3/ext/packet' if RUBY_ENGINE == 'ruby' and !ENV['OPENC3_NO_EXT']
|
|
@@ -252,7 +252,7 @@ module OpenC3
|
|
|
252
252
|
#######################################################################
|
|
253
253
|
when 'STATE', 'READ_CONVERSION', 'WRITE_CONVERSION', 'POLY_READ_CONVERSION',\
|
|
254
254
|
'POLY_WRITE_CONVERSION', 'SEG_POLY_READ_CONVERSION', 'SEG_POLY_WRITE_CONVERSION',\
|
|
255
|
-
'GENERIC_READ_CONVERSION_START', 'GENERIC_WRITE_CONVERSION_START', 'REQUIRED',\
|
|
255
|
+
'GENERIC_READ_CONVERSION_START', 'GENERIC_WRITE_CONVERSION_START', 'CONVERTED_DATA', 'REQUIRED',\
|
|
256
256
|
'LIMITS', 'LIMITS_RESPONSE', 'UNITS', 'FORMAT_STRING', 'DESCRIPTION',\
|
|
257
257
|
'MINIMUM_VALUE', 'MAXIMUM_VALUE', 'DEFAULT_VALUE', 'OVERFLOW', 'OVERLAP', 'KEY', 'VARIABLE_BIT_SIZE',\
|
|
258
258
|
'OBFUSCATE'
|
|
@@ -675,11 +675,6 @@ module OpenC3
|
|
|
675
675
|
klass = OpenC3.require_class(params[0])
|
|
676
676
|
conversion = klass.new(*params[1..(params.length - 1)])
|
|
677
677
|
@current_item.public_send("#{keyword.downcase}=".to_sym, conversion)
|
|
678
|
-
if klass != ProcessorConversion and (conversion.converted_type.nil? or conversion.converted_bit_size.nil?)
|
|
679
|
-
msg = "Read Conversion #{params[0]} on item #{@current_item.name} does not specify converted type or bit size"
|
|
680
|
-
@warnings << msg
|
|
681
|
-
Logger.instance.warn @warnings[-1]
|
|
682
|
-
end
|
|
683
678
|
else
|
|
684
679
|
conversion = PythonProxy.new('Conversion', params[0], *params[1..(params.length - 1)])
|
|
685
680
|
@current_item.public_send("#{keyword.downcase}=".to_sym, conversion)
|
|
@@ -719,8 +714,9 @@ module OpenC3
|
|
|
719
714
|
# All config.lines following this config.line are considered part
|
|
720
715
|
# of the conversion until an end of conversion marker is found
|
|
721
716
|
when 'GENERIC_READ_CONVERSION_START', 'GENERIC_WRITE_CONVERSION_START'
|
|
722
|
-
|
|
723
|
-
|
|
717
|
+
# As of COSMOS 7 the converted type and bit size are deprecated
|
|
718
|
+
# but we're still allowing them to be defined as parameters for backward compatibility
|
|
719
|
+
parser.verify_num_parameters(0, 2, keyword)
|
|
724
720
|
@proc_text = ''
|
|
725
721
|
@building_generic_conversion = true
|
|
726
722
|
parser.set_preserve_lines(true)
|
|
@@ -731,10 +727,30 @@ module OpenC3
|
|
|
731
727
|
raise parser.error("Invalid converted_type: #{@converted_type}.") unless CONVERTED_DATA_TYPES.include? @converted_type
|
|
732
728
|
end
|
|
733
729
|
@converted_bit_size = Integer(params[1]) if params[1]
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
730
|
+
|
|
731
|
+
# Define the converted data type, bit size, and optional array size
|
|
732
|
+
# for items with read conversions (especially DERIVED items)
|
|
733
|
+
when 'CONVERTED_DATA'
|
|
734
|
+
usage = "CONVERTED_DATA <Converted Bit Size> <Converted Type> <Converted Array Size (optional)>"
|
|
735
|
+
parser.verify_num_parameters(2, 3, usage)
|
|
736
|
+
raise parser.error("#{keyword} requires a current item") unless @current_item
|
|
737
|
+
raise parser.error("#{keyword} requires a current item with a conversion") unless @current_item.read_conversion or @current_item.write_conversion
|
|
738
|
+
converted_bit_size = Integer(params[0])
|
|
739
|
+
converted_type = params[1].upcase.intern
|
|
740
|
+
raise parser.error("Invalid converted_type: #{converted_type}.") unless CONVERTED_DATA_TYPES.include? converted_type
|
|
741
|
+
if @current_item.read_conversion
|
|
742
|
+
@current_item.read_conversion.converted_type = converted_type
|
|
743
|
+
@current_item.read_conversion.converted_bit_size = converted_bit_size
|
|
744
|
+
if params[2]
|
|
745
|
+
@current_item.read_conversion.converted_array_size = Integer(params[2])
|
|
746
|
+
end
|
|
747
|
+
end
|
|
748
|
+
if @current_item.write_conversion
|
|
749
|
+
@current_item.write_conversion.converted_type = converted_type
|
|
750
|
+
@current_item.write_conversion.converted_bit_size = converted_bit_size
|
|
751
|
+
if params[2]
|
|
752
|
+
@current_item.write_conversion.converted_array_size = Integer(params[2])
|
|
753
|
+
end
|
|
738
754
|
end
|
|
739
755
|
|
|
740
756
|
# Define a set of limits for the current telemetry item
|
|
@@ -52,6 +52,14 @@ module OpenC3
|
|
|
52
52
|
return _cal_handle_response(response, 'Failed to delete timeline')
|
|
53
53
|
end
|
|
54
54
|
|
|
55
|
+
# Creates an activity for the specified timeline.
|
|
56
|
+
#
|
|
57
|
+
# @param name [String] The name of the timeline.
|
|
58
|
+
# @param kind [String] The kind of activity. Must be one of "COMMAND", "SCRIPT", or "RESERVE".
|
|
59
|
+
# @param start [DateTime] The start time of the activity.
|
|
60
|
+
# @param stop [DateTime] The stop time of the activity.
|
|
61
|
+
# @param data [Hash, optional] Additional data to associate with the activity. Defaults to {}. Any activity can provide "username", "notes", and "customTitle". "command", "script", and "reserve" keys are reserves for the corresponding activity kind, with "environment" also available for script activities.
|
|
62
|
+
# @param scope [String, optional] The scope of the activity. Defaults to OPENC3_SCOPE, must correspond to the timeline.
|
|
55
63
|
def create_timeline_activity(name, kind:, start:, stop:, data: {}, scope: $openc3_scope)
|
|
56
64
|
kind = kind.to_s.downcase()
|
|
57
65
|
kinds = %w(command script reserve)
|
data/lib/openc3/script/script.rb
CHANGED
|
@@ -47,6 +47,9 @@ $disconnect = false
|
|
|
47
47
|
$openc3_scope = ENV['OPENC3_SCOPE'] || 'DEFAULT'
|
|
48
48
|
$openc3_in_cluster = false
|
|
49
49
|
|
|
50
|
+
saved_verbose = $VERBOSE
|
|
51
|
+
$VERBOSE = false
|
|
52
|
+
|
|
50
53
|
module OpenC3
|
|
51
54
|
module Script
|
|
52
55
|
private
|
|
@@ -177,6 +180,10 @@ module OpenC3
|
|
|
177
180
|
message_box(string, *items, **options)
|
|
178
181
|
end
|
|
179
182
|
|
|
183
|
+
def check_box(string, *items, **options)
|
|
184
|
+
message_box(string, *items, **options)
|
|
185
|
+
end
|
|
186
|
+
|
|
180
187
|
def _file_dialog(title, message, filter:)
|
|
181
188
|
answer = ''
|
|
182
189
|
path = "./*"
|
|
@@ -199,6 +206,16 @@ module OpenC3
|
|
|
199
206
|
_file_dialog(title, message, filter)
|
|
200
207
|
end
|
|
201
208
|
|
|
209
|
+
def open_bucket_dialog(title, message = "Open Bucket File")
|
|
210
|
+
answer = ''
|
|
211
|
+
while answer.empty?
|
|
212
|
+
print "#{title}\n#{message}\n<Type bucket file path (e.g. BUCKET/path/to/file)>:"
|
|
213
|
+
answer = gets
|
|
214
|
+
answer.chomp!
|
|
215
|
+
end
|
|
216
|
+
return answer
|
|
217
|
+
end
|
|
218
|
+
|
|
202
219
|
def prompt(string, text_color: nil, background_color: nil, font_size: nil, font_family: nil, details: nil)
|
|
203
220
|
print "#{string}: "
|
|
204
221
|
print "Details: #{details}\n" if details
|
|
@@ -363,3 +380,5 @@ module OpenC3
|
|
|
363
380
|
end
|
|
364
381
|
end
|
|
365
382
|
end
|
|
383
|
+
|
|
384
|
+
$VERBOSE = saved_verbose
|
|
@@ -110,7 +110,7 @@ module OpenC3
|
|
|
110
110
|
end
|
|
111
111
|
|
|
112
112
|
return _get_storage_file("#{part}/#{path}", scope: scope)
|
|
113
|
-
rescue
|
|
113
|
+
rescue
|
|
114
114
|
if part == "targets_modified"
|
|
115
115
|
part = "targets"
|
|
116
116
|
redo
|
|
@@ -141,13 +141,13 @@ module OpenC3
|
|
|
141
141
|
return result['url']
|
|
142
142
|
end
|
|
143
143
|
|
|
144
|
-
def _get_storage_file(path, scope: $openc3_scope)
|
|
144
|
+
def _get_storage_file(path, bucket: 'OPENC3_CONFIG_BUCKET', scope: $openc3_scope)
|
|
145
145
|
# Create Tempfile to store data
|
|
146
146
|
file = Tempfile.new('target', binmode: true)
|
|
147
147
|
file.filename = path
|
|
148
148
|
|
|
149
149
|
endpoint = "/openc3-api/storage/download/#{scope}/#{path}"
|
|
150
|
-
result = _get_presigned_request(endpoint, scope: scope)
|
|
150
|
+
result = _get_presigned_request(endpoint, bucket: bucket, scope: scope)
|
|
151
151
|
puts "Reading #{scope}/#{path}"
|
|
152
152
|
|
|
153
153
|
# Try to get the file
|
|
@@ -186,11 +186,11 @@ module OpenC3
|
|
|
186
186
|
end
|
|
187
187
|
end
|
|
188
188
|
|
|
189
|
-
def _get_presigned_request(endpoint, external: nil, scope: $openc3_scope)
|
|
189
|
+
def _get_presigned_request(endpoint, external: nil, bucket: 'OPENC3_CONFIG_BUCKET', scope: $openc3_scope)
|
|
190
190
|
if external or !$openc3_in_cluster
|
|
191
|
-
response = $api_server.request('get', endpoint, query: { bucket:
|
|
191
|
+
response = $api_server.request('get', endpoint, query: { bucket: bucket }, scope: scope)
|
|
192
192
|
else
|
|
193
|
-
response = $api_server.request('get', endpoint, query: { bucket:
|
|
193
|
+
response = $api_server.request('get', endpoint, query: { bucket: bucket, internal: true }, scope: scope)
|
|
194
194
|
end
|
|
195
195
|
if response.nil? || response.status != 201
|
|
196
196
|
raise "Failed to get presigned URL for #{endpoint}"
|
|
@@ -121,7 +121,7 @@ module OpenC3
|
|
|
121
121
|
# Connect to the websocket with authorization in query params
|
|
122
122
|
def connect
|
|
123
123
|
disconnect()
|
|
124
|
-
final_url = @url + "?scope=#{@scope}&authorization=#{@authentication.
|
|
124
|
+
final_url = @url + "?scope=#{@scope}&authorization=#{@authentication.get_otp(scope: @scope)}"
|
|
125
125
|
@stream = WebSocketClientStream.new(final_url, @write_timeout, @read_timeout, @connect_timeout)
|
|
126
126
|
@stream.headers = {
|
|
127
127
|
'Sec-WebSocket-Protocol' => 'actioncable-v1-json, actioncable-unsupported',
|
data/lib/openc3/system/system.rb
CHANGED
|
@@ -81,22 +81,22 @@ module OpenC3
|
|
|
81
81
|
# Nothing to do if there are no targets
|
|
82
82
|
return if target_names.nil? or target_names.length == 0
|
|
83
83
|
if @@instance.nil?
|
|
84
|
-
|
|
84
|
+
targets_path = "#{base_dir}/_targets"
|
|
85
|
+
FileUtils.mkdir_p(targets_path)
|
|
85
86
|
bucket = Bucket.getClient()
|
|
86
87
|
target_names.each do |target_name|
|
|
87
88
|
# Retrieve bucket/targets/target_name/<TARGET>_current.zip
|
|
88
|
-
zip_path = "#{
|
|
89
|
+
zip_path = "#{targets_path}/#{target_name}_current.zip"
|
|
89
90
|
FileUtils.mkdir_p(File.dirname(zip_path))
|
|
90
91
|
bucket_key = "#{scope}/target_archives/#{target_name}/#{target_name}_current.zip"
|
|
91
92
|
Logger.info("Retrieving #{bucket_key} from targets bucket")
|
|
92
93
|
bucket.get_object(bucket: ENV['OPENC3_CONFIG_BUCKET'], key: bucket_key, path: zip_path)
|
|
93
|
-
targets_path = "#{base_dir}/targets"
|
|
94
|
-
FileUtils.mkdir_p(targets_path)
|
|
95
94
|
Zip::File.open(zip_path) do |zip_file|
|
|
96
95
|
zip_file.each do |entry|
|
|
97
96
|
zip_file.extract(entry.name, destination_directory: targets_path)
|
|
98
97
|
end
|
|
99
98
|
end
|
|
99
|
+
FileUtils.rm(zip_path) if File.exist?(zip_path)
|
|
100
100
|
|
|
101
101
|
# Now add any modifications in targets_modified/TARGET/cmd_tlm
|
|
102
102
|
# This adds support for remembering dynamically created packets
|
|
@@ -106,13 +106,13 @@ module OpenC3
|
|
|
106
106
|
_, files = bucket.list_files(bucket: ENV['OPENC3_CONFIG_BUCKET'], path: bucket_path)
|
|
107
107
|
files.each do |file|
|
|
108
108
|
bucket_key = File.join(bucket_path, file['name'])
|
|
109
|
-
local_path = "#{
|
|
109
|
+
local_path = "#{targets_path}/#{target_name}/cmd_tlm/#{file['name']}"
|
|
110
110
|
bucket.get_object(bucket: ENV['OPENC3_CONFIG_BUCKET'], key: bucket_key, path: local_path)
|
|
111
111
|
end
|
|
112
112
|
end
|
|
113
113
|
|
|
114
114
|
# Build System from targets
|
|
115
|
-
System.instance(target_names,
|
|
115
|
+
System.instance(target_names, targets_path)
|
|
116
116
|
end
|
|
117
117
|
end
|
|
118
118
|
|
|
@@ -221,7 +221,6 @@ module OpenC3
|
|
|
221
221
|
else
|
|
222
222
|
Logger.error connect_error.formatted
|
|
223
223
|
unless @connection_failed_messages.include?(connect_error.message)
|
|
224
|
-
OpenC3.write_exception_file(connect_error)
|
|
225
224
|
@connection_failed_messages << connect_error.message
|
|
226
225
|
end
|
|
227
226
|
end
|
|
@@ -242,7 +241,6 @@ module OpenC3
|
|
|
242
241
|
else
|
|
243
242
|
Logger.error err.formatted
|
|
244
243
|
unless @connection_lost_messages.include?(err.message)
|
|
245
|
-
OpenC3.write_exception_file(err)
|
|
246
244
|
@connection_lost_messages << err.message
|
|
247
245
|
end
|
|
248
246
|
end
|
data/lib/openc3/top_level.rb
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# encoding:
|
|
1
|
+
# encoding: utf-8
|
|
2
2
|
|
|
3
3
|
# Copyright 2022 Ball Aerospace & Technologies Corp.
|
|
4
4
|
# All Rights Reserved.
|
|
@@ -101,6 +101,20 @@ module OpenC3
|
|
|
101
101
|
end
|
|
102
102
|
end
|
|
103
103
|
|
|
104
|
+
def self.sanitize_path(path)
|
|
105
|
+
return '' if path.nil?
|
|
106
|
+
# path is passed as a parameter thus we have to sanitize it or the code scanner detects:
|
|
107
|
+
# "Uncontrolled data used in path expression"
|
|
108
|
+
# This method is taken directly from the Rails source:
|
|
109
|
+
# https://api.rubyonrails.org/v5.2/classes/ActiveStorage/Filename.html#method-i-sanitized
|
|
110
|
+
# NOTE: I removed the '/' character because we have to allow this in order to traverse the path
|
|
111
|
+
sanitized = path.encode(Encoding::UTF_8, invalid: :replace, undef: :replace, replace: "�").strip.tr("\u{202E}%$|:;\t\r\n\\", "-").gsub('..', '-')
|
|
112
|
+
if sanitized != path
|
|
113
|
+
raise StorageError, "Invalid path: #{path}"
|
|
114
|
+
end
|
|
115
|
+
sanitized
|
|
116
|
+
end
|
|
117
|
+
|
|
104
118
|
require 'openc3/utilities/logger'
|
|
105
119
|
|
|
106
120
|
# Creates a marshal file by serializing the given obj
|
|
@@ -280,65 +294,6 @@ module OpenC3
|
|
|
280
294
|
return log_file
|
|
281
295
|
end
|
|
282
296
|
|
|
283
|
-
# Writes a log file with information about the current configuration
|
|
284
|
-
# including the Ruby version, OpenC3 version, whether you are on Windows, the
|
|
285
|
-
# OpenC3 path, and the Ruby path along with the exception that
|
|
286
|
-
# is passed in.
|
|
287
|
-
#
|
|
288
|
-
# @param [String] filename String to append to the exception log filename.
|
|
289
|
-
# The filename will start with a date/time stamp.
|
|
290
|
-
# @param [String] log_dir By default this method will write to the OpenC3
|
|
291
|
-
# default log directory. By setting this parameter you can override the
|
|
292
|
-
# directory the log will be written to.
|
|
293
|
-
# @return [String|nil] The fully pathed log filename or nil if there was
|
|
294
|
-
# an error creating the log file.
|
|
295
|
-
def self.write_exception_file(exception, filename = 'exception', log_dir = nil)
|
|
296
|
-
log_file = create_log_file(filename, log_dir) do |file|
|
|
297
|
-
file.puts "Exception:"
|
|
298
|
-
if exception
|
|
299
|
-
file.puts exception.formatted
|
|
300
|
-
file.puts
|
|
301
|
-
else
|
|
302
|
-
file.puts "No Exception Given"
|
|
303
|
-
file.puts caller.join("\n")
|
|
304
|
-
file.puts
|
|
305
|
-
end
|
|
306
|
-
file.puts "Caller Backtrace:"
|
|
307
|
-
file.puts caller().join("\n")
|
|
308
|
-
file.puts
|
|
309
|
-
|
|
310
|
-
file.puts "Ruby Version: ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE} patchlevel #{RUBY_PATCHLEVEL}) [#{RUBY_PLATFORM}]"
|
|
311
|
-
file.puts "Rubygems Version: #{Gem::VERSION}"
|
|
312
|
-
file.puts "OpenC3 Version: #{OpenC3::VERSION}"
|
|
313
|
-
file.puts "OpenC3::PATH: #{OpenC3::PATH}"
|
|
314
|
-
file.puts ""
|
|
315
|
-
file.puts "Environment:"
|
|
316
|
-
file.puts "RUBYOPT: #{ENV['RUBYOPT']}"
|
|
317
|
-
file.puts "RUBYLIB: #{ENV['RUBYLIB']}"
|
|
318
|
-
file.puts "GEM_PATH: #{ENV['GEM_PATH']}"
|
|
319
|
-
file.puts "GEMRC: #{ENV['GEMRC']}"
|
|
320
|
-
file.puts "RI_DEVKIT: #{ENV['RI_DEVKIT']}"
|
|
321
|
-
file.puts "GEM_HOME: #{ENV['GEM_HOME']}"
|
|
322
|
-
file.puts "PYTHONUSERBASE: #{ENV['PYTHONUSERBASE']}"
|
|
323
|
-
file.puts "PATH: #{ENV['PATH']}"
|
|
324
|
-
file.puts ""
|
|
325
|
-
file.puts "Ruby Path:\n #{$:.join("\n ")}\n\n"
|
|
326
|
-
file.puts "Gems:"
|
|
327
|
-
Gem.loaded_specs.values.map { |x| file.puts "#{x.name} #{x.version} #{x.platform}" }
|
|
328
|
-
file.puts ""
|
|
329
|
-
file.puts "All Threads Backtraces:"
|
|
330
|
-
Thread.list.each do |thread|
|
|
331
|
-
file.puts thread.backtrace.join("\n")
|
|
332
|
-
file.puts
|
|
333
|
-
end
|
|
334
|
-
file.puts ""
|
|
335
|
-
file.puts ""
|
|
336
|
-
ensure
|
|
337
|
-
file.close
|
|
338
|
-
end
|
|
339
|
-
return log_file
|
|
340
|
-
end
|
|
341
|
-
|
|
342
297
|
# Writes a log file with information about unexpected output
|
|
343
298
|
#
|
|
344
299
|
# @param [String] text The unexpected output text
|
|
@@ -367,7 +322,6 @@ module OpenC3
|
|
|
367
322
|
def self.handle_fatal_exception(error, _try_gui = true)
|
|
368
323
|
unless SystemExit === error or SignalException === error
|
|
369
324
|
$openc3_fatal_exception = error
|
|
370
|
-
self.write_exception_file(error)
|
|
371
325
|
Logger.fatal "Fatal Exception! Exiting..."
|
|
372
326
|
Logger.fatal error.formatted
|
|
373
327
|
if $stdout != STDOUT
|
|
@@ -392,7 +346,6 @@ module OpenC3
|
|
|
392
346
|
# @param try_gui [Boolean] Whether to try and create a GUI exception popup
|
|
393
347
|
def self.handle_critical_exception(error, _try_gui = true)
|
|
394
348
|
Logger.error "Critical Exception! #{error.formatted}"
|
|
395
|
-
self.write_exception_file(error)
|
|
396
349
|
end
|
|
397
350
|
|
|
398
351
|
# Creates a Ruby Thread to run the given block. Rescues any exceptions and
|
|
@@ -412,7 +365,6 @@ module OpenC3
|
|
|
412
365
|
Logger.error e.formatted
|
|
413
366
|
retry_count += 1
|
|
414
367
|
if retry_count <= retry_attempts
|
|
415
|
-
self.write_exception_file(e)
|
|
416
368
|
retry
|
|
417
369
|
end
|
|
418
370
|
handle_fatal_exception(e)
|
|
@@ -53,7 +53,7 @@ module OpenC3
|
|
|
53
53
|
limits['red_high'] = event[:red_high]
|
|
54
54
|
limits['green_low'] = event[:green_low] if event[:green_low] && event[:green_high]
|
|
55
55
|
limits['green_high'] = event[:green_high] if event[:green_low] && event[:green_high]
|
|
56
|
-
limits_settings[event[:limits_set]] = limits
|
|
56
|
+
limits_settings[event[:limits_set].to_s] = limits
|
|
57
57
|
limits_settings['persistence_setting'] = event[:persistence] if event[:persistence]
|
|
58
58
|
limits_settings['enabled'] = event[:enabled] if not event[:enabled].nil?
|
|
59
59
|
Store.hset("#{scope}__current_limits_settings", field, JSON.generate(limits_settings, allow_nan: true))
|
|
@@ -33,8 +33,10 @@ module OpenC3
|
|
|
33
33
|
raise OpenC3AuthenticationError, "Authentication requires environment variable OPENC3_API_PASSWORD"
|
|
34
34
|
end
|
|
35
35
|
@service = password == ENV['OPENC3_SERVICE_PASSWORD']
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
retry_faraday_request do
|
|
37
|
+
response = _make_auth_request(password)
|
|
38
|
+
@token = response.body
|
|
39
|
+
end
|
|
38
40
|
if @token.nil? or @token.empty?
|
|
39
41
|
raise OpenC3AuthenticationError, "Authentication failed. Please check the password in the environment variable OPENC3_API_PASSWORD"
|
|
40
42
|
end
|
|
@@ -45,23 +47,60 @@ module OpenC3
|
|
|
45
47
|
@token
|
|
46
48
|
end
|
|
47
49
|
|
|
50
|
+
def get_otp(scope: 'DEFAULT')
|
|
51
|
+
session_token = token()
|
|
52
|
+
if session_token.nil? or session_token.empty?
|
|
53
|
+
raise OpenC3AuthenticationError, "Uninitialized authentication: unable to get OTP"
|
|
54
|
+
end
|
|
55
|
+
retry_faraday_request do
|
|
56
|
+
response = _make_otp_request(scope: scope)
|
|
57
|
+
return response.body
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
48
61
|
def _make_auth_request(password)
|
|
49
62
|
Faraday.new.post(_generate_auth_url, '{"password": "' + password + '"}', {'Content-Type' => 'application/json'})
|
|
50
63
|
end
|
|
51
64
|
|
|
52
|
-
def
|
|
65
|
+
def _make_otp_request(scope: 'DEFAULT')
|
|
66
|
+
params = {
|
|
67
|
+
'scope' => scope
|
|
68
|
+
}
|
|
69
|
+
headers = {
|
|
70
|
+
'Authorization' => token,
|
|
71
|
+
}
|
|
72
|
+
Faraday.new.get(_generate_auth_url('/auth/otp'), params, headers)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def _generate_auth_url(endpoint = nil)
|
|
53
76
|
schema = ENV['OPENC3_API_SCHEMA'] || 'http'
|
|
54
77
|
hostname = ENV['OPENC3_API_HOSTNAME'] || (ENV['OPENC3_DEVEL'] ? '127.0.0.1' : 'openc3-cosmos-cmd-tlm-api')
|
|
55
78
|
port = ENV['OPENC3_API_PORT'] || '2901'
|
|
56
79
|
port = port.to_i
|
|
57
|
-
endpoint
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
80
|
+
unless endpoint
|
|
81
|
+
endpoint = if @service
|
|
82
|
+
"auth/verify_service"
|
|
83
|
+
else
|
|
84
|
+
"auth/verify"
|
|
85
|
+
end
|
|
61
86
|
end
|
|
62
87
|
return "#{schema}://#{hostname}:#{port}/openc3-api/#{endpoint}"
|
|
63
88
|
end
|
|
64
89
|
|
|
90
|
+
def retry_faraday_request(max_retries: 3)
|
|
91
|
+
retries = 0
|
|
92
|
+
begin
|
|
93
|
+
yield
|
|
94
|
+
rescue Faraday::ConnectionFailed, Faraday::TimeoutError => e
|
|
95
|
+
retries += 1
|
|
96
|
+
if retries <= max_retries
|
|
97
|
+
STDOUT.puts "Authentication request failed (attempt #{retries}/3): #{e.message}. Retrying in #{retries}s..."
|
|
98
|
+
sleep(retries)
|
|
99
|
+
retry
|
|
100
|
+
end
|
|
101
|
+
raise
|
|
102
|
+
end
|
|
103
|
+
end
|
|
65
104
|
end
|
|
66
105
|
|
|
67
106
|
# OpenC3 enterprise Keycloak authentication code
|