openc3 7.2.1 → 7.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (502) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +1 -1
  3. data/Guardfile +0 -0
  4. data/LICENSE.md +0 -0
  5. data/README.md +0 -0
  6. data/Rakefile +0 -0
  7. data/bin/openc3cli +235 -6
  8. data/bin/pipinstall +5 -4
  9. data/bin/pipuninstall +5 -1
  10. data/bin/uvinstall +156 -0
  11. data/data/config/_array_params.yaml +0 -0
  12. data/data/config/_canvas_values.yaml +0 -0
  13. data/data/config/_graph_params.yaml +0 -0
  14. data/data/config/_id_items.yaml +0 -0
  15. data/data/config/_id_params.yaml +0 -0
  16. data/data/config/_interfaces.yaml +0 -0
  17. data/data/config/_items.yaml +0 -0
  18. data/data/config/_params.yaml +0 -0
  19. data/data/config/command.yaml +0 -0
  20. data/data/config/command_modifiers.yaml +2 -0
  21. data/data/config/command_telemetry.yaml +0 -0
  22. data/data/config/conversions.yaml +0 -0
  23. data/data/config/graph_settings.yaml +0 -0
  24. data/data/config/interface_modifiers.yaml +103 -1
  25. data/data/config/item_modifiers.yaml +2 -1
  26. data/data/config/microservice.yaml +9 -2
  27. data/data/config/param_item_modifiers.yaml +0 -0
  28. data/data/config/parameter_modifiers.yaml +0 -0
  29. data/data/config/plugins.yaml +0 -0
  30. data/data/config/processors.yaml +0 -0
  31. data/data/config/protocols.yaml +0 -0
  32. data/data/config/screen.yaml +2 -1
  33. data/data/config/settings.yaml +0 -0
  34. data/data/config/table_manager.yaml +0 -0
  35. data/data/config/table_parameter_modifiers.yaml +0 -0
  36. data/data/config/target.yaml +21 -0
  37. data/data/config/target_config.yaml +0 -0
  38. data/data/config/telemetry.yaml +0 -0
  39. data/data/config/telemetry_modifiers.yaml +2 -0
  40. data/data/config/tool.yaml +3 -1
  41. data/data/config/unknown.yaml +0 -0
  42. data/data/config/widgets.yaml +22 -3
  43. data/data/xtce_schemas/SpaceSystem_20180204.xsd +5918 -0
  44. data/data/xtce_schemas/xml.xsd +116 -0
  45. data/ext/openc3/ext/array/array.c +0 -0
  46. data/ext/openc3/ext/array/extconf.rb +0 -0
  47. data/ext/openc3/ext/buffered_file/buffered_file.c +0 -0
  48. data/ext/openc3/ext/buffered_file/extconf.rb +0 -0
  49. data/ext/openc3/ext/burst_protocol/burst_protocol.c +6 -0
  50. data/ext/openc3/ext/burst_protocol/extconf.rb +0 -0
  51. data/ext/openc3/ext/config_parser/config_parser.c +0 -0
  52. data/ext/openc3/ext/config_parser/extconf.rb +0 -0
  53. data/ext/openc3/ext/crc/crc.c +0 -0
  54. data/ext/openc3/ext/crc/extconf.rb +0 -0
  55. data/ext/openc3/ext/openc3_io/extconf.rb +0 -0
  56. data/ext/openc3/ext/openc3_io/openc3_io.c +0 -0
  57. data/ext/openc3/ext/packet/extconf.rb +0 -0
  58. data/ext/openc3/ext/packet/packet.c +0 -0
  59. data/ext/openc3/ext/platform/extconf.rb +0 -0
  60. data/ext/openc3/ext/platform/platform.c +4 -1
  61. data/ext/openc3/ext/polynomial_conversion/extconf.rb +0 -0
  62. data/ext/openc3/ext/polynomial_conversion/polynomial_conversion.c +0 -0
  63. data/ext/openc3/ext/string/extconf.rb +0 -0
  64. data/ext/openc3/ext/string/string.c +0 -0
  65. data/ext/openc3/ext/structure/structure.c +49 -17
  66. data/ext/openc3/ext/tabbed_plots_config/extconf.rb +0 -0
  67. data/ext/openc3/ext/tabbed_plots_config/tabbed_plots_config.c +0 -0
  68. data/ext/openc3/ext/telemetry/extconf.rb +0 -0
  69. data/ext/openc3/ext/telemetry/telemetry.c +1 -1
  70. data/lib/cosmos.rb +0 -0
  71. data/lib/cosmosc2.rb +0 -0
  72. data/lib/openc3/accessors/accessor.rb +0 -0
  73. data/lib/openc3/accessors/binary_accessor.rb +63 -9
  74. data/lib/openc3/accessors/cbor_accessor.rb +0 -0
  75. data/lib/openc3/accessors/form_accessor.rb +0 -0
  76. data/lib/openc3/accessors/html_accessor.rb +0 -0
  77. data/lib/openc3/accessors/http_accessor.rb +0 -0
  78. data/lib/openc3/accessors/json_accessor.rb +0 -0
  79. data/lib/openc3/accessors/template_accessor.rb +2 -2
  80. data/lib/openc3/accessors/xml_accessor.rb +0 -0
  81. data/lib/openc3/accessors.rb +0 -0
  82. data/lib/openc3/api/README.md +0 -0
  83. data/lib/openc3/api/api.rb +0 -0
  84. data/lib/openc3/api/authorized_api.rb +0 -0
  85. data/lib/openc3/api/calendar_api.rb +0 -0
  86. data/lib/openc3/api/cmd_api.rb +1 -1
  87. data/lib/openc3/api/config_api.rb +0 -0
  88. data/lib/openc3/api/interface_api.rb +2 -2
  89. data/lib/openc3/api/limits_api.rb +27 -13
  90. data/lib/openc3/api/metrics_api.rb +1 -1
  91. data/lib/openc3/api/offline_access_api.rb +1 -1
  92. data/lib/openc3/api/router_api.rb +3 -3
  93. data/lib/openc3/api/settings_api.rb +22 -18
  94. data/lib/openc3/api/stash_api.rb +1 -1
  95. data/lib/openc3/api/target_api.rb +0 -0
  96. data/lib/openc3/api/tlm_api.rb +5 -4
  97. data/lib/openc3/bridge/bridge.rb +0 -0
  98. data/lib/openc3/bridge/bridge_config.rb +0 -0
  99. data/lib/openc3/bridge/bridge_interface_thread.rb +0 -0
  100. data/lib/openc3/bridge/bridge_router_thread.rb +0 -0
  101. data/lib/openc3/ccsds/ccsds_packet.rb +0 -0
  102. data/lib/openc3/ccsds/ccsds_parser.rb +0 -0
  103. data/lib/openc3/config/config_parser.rb +78 -17
  104. data/lib/openc3/config/meta_config_parser.rb +0 -0
  105. data/lib/openc3/conversions/bit_reverse_conversion.rb +0 -0
  106. data/lib/openc3/conversions/conversion.rb +0 -0
  107. data/lib/openc3/conversions/generic_conversion.rb +0 -0
  108. data/lib/openc3/conversions/ip_read_conversion.rb +0 -0
  109. data/lib/openc3/conversions/ip_write_conversion.rb +0 -0
  110. data/lib/openc3/conversions/object_read_conversion.rb +0 -0
  111. data/lib/openc3/conversions/object_write_conversion.rb +0 -0
  112. data/lib/openc3/conversions/packet_time_formatted_conversion.rb +0 -0
  113. data/lib/openc3/conversions/packet_time_seconds_conversion.rb +0 -0
  114. data/lib/openc3/conversions/polynomial_conversion.rb +0 -0
  115. data/lib/openc3/conversions/processor_conversion.rb +0 -0
  116. data/lib/openc3/conversions/received_count_conversion.rb +0 -0
  117. data/lib/openc3/conversions/received_time_formatted_conversion.rb +0 -0
  118. data/lib/openc3/conversions/received_time_seconds_conversion.rb +0 -0
  119. data/lib/openc3/conversions/segmented_polynomial_conversion.rb +0 -0
  120. data/lib/openc3/conversions/unix_time_conversion.rb +0 -0
  121. data/lib/openc3/conversions/unix_time_formatted_conversion.rb +0 -0
  122. data/lib/openc3/conversions/unix_time_seconds_conversion.rb +0 -0
  123. data/lib/openc3/conversions.rb +0 -0
  124. data/lib/openc3/core_ext/array.rb +0 -0
  125. data/lib/openc3/core_ext/binding.rb +0 -0
  126. data/lib/openc3/core_ext/class.rb +0 -0
  127. data/lib/openc3/core_ext/exception.rb +0 -0
  128. data/lib/openc3/core_ext/faraday.rb +0 -0
  129. data/lib/openc3/core_ext/file.rb +0 -0
  130. data/lib/openc3/core_ext/io.rb +0 -0
  131. data/lib/openc3/core_ext/kernel.rb +0 -0
  132. data/lib/openc3/core_ext/math.rb +0 -0
  133. data/lib/openc3/core_ext/matrix.rb +0 -0
  134. data/lib/openc3/core_ext/objectspace.rb +0 -0
  135. data/lib/openc3/core_ext/openc3_io.rb +0 -0
  136. data/lib/openc3/core_ext/range.rb +0 -0
  137. data/lib/openc3/core_ext/socket.rb +0 -0
  138. data/lib/openc3/core_ext/string.rb +0 -0
  139. data/lib/openc3/core_ext/stringio.rb +0 -0
  140. data/lib/openc3/core_ext/tempfile.rb +0 -0
  141. data/lib/openc3/core_ext/time.rb +0 -0
  142. data/lib/openc3/core_ext.rb +0 -0
  143. data/lib/openc3/ext/.keep +0 -0
  144. data/lib/openc3/interfaces/file_interface.rb +0 -0
  145. data/lib/openc3/interfaces/http_client_interface.rb +1 -1
  146. data/lib/openc3/interfaces/http_server_interface.rb +0 -0
  147. data/lib/openc3/interfaces/interface.rb +5 -4
  148. data/lib/openc3/interfaces/mqtt_interface.rb +0 -0
  149. data/lib/openc3/interfaces/mqtt_stream_interface.rb +2 -0
  150. data/lib/openc3/interfaces/protocols/burst_protocol.rb +27 -0
  151. data/lib/openc3/interfaces/protocols/cmd_response_protocol.rb +0 -0
  152. data/lib/openc3/interfaces/protocols/cobs_protocol.rb +0 -0
  153. data/lib/openc3/interfaces/protocols/crc_protocol.rb +0 -0
  154. data/lib/openc3/interfaces/protocols/fixed_protocol.rb +0 -0
  155. data/lib/openc3/interfaces/protocols/ignore_packet_protocol.rb +0 -0
  156. data/lib/openc3/interfaces/protocols/length_protocol.rb +7 -0
  157. data/lib/openc3/interfaces/protocols/preidentified_protocol.rb +7 -0
  158. data/lib/openc3/interfaces/protocols/protocol.rb +4 -4
  159. data/lib/openc3/interfaces/protocols/slip_protocol.rb +0 -0
  160. data/lib/openc3/interfaces/protocols/template_protocol.rb +2 -2
  161. data/lib/openc3/interfaces/protocols/terminated_protocol.rb +0 -0
  162. data/lib/openc3/interfaces/serial_interface.rb +2 -0
  163. data/lib/openc3/interfaces/simulated_target_interface.rb +0 -0
  164. data/lib/openc3/interfaces/stream_interface.rb +0 -0
  165. data/lib/openc3/interfaces/tcpip_client_interface.rb +2 -0
  166. data/lib/openc3/interfaces/tcpip_server_interface.rb +0 -0
  167. data/lib/openc3/interfaces/udp_interface.rb +16 -5
  168. data/lib/openc3/interfaces.rb +0 -0
  169. data/lib/openc3/io/buffered_file.rb +0 -0
  170. data/lib/openc3/io/io_multiplexer.rb +0 -0
  171. data/lib/openc3/io/json_api.rb +3 -2
  172. data/lib/openc3/io/json_api_object.rb +0 -0
  173. data/lib/openc3/io/json_drb.rb +44 -12
  174. data/lib/openc3/io/json_drb_object.rb +0 -0
  175. data/lib/openc3/io/json_drb_rack.rb +0 -0
  176. data/lib/openc3/io/json_rpc.rb +0 -2
  177. data/lib/openc3/io/posix_serial_driver.rb +0 -0
  178. data/lib/openc3/io/serial_driver.rb +0 -0
  179. data/lib/openc3/io/stderr.rb +0 -0
  180. data/lib/openc3/io/stdout.rb +0 -0
  181. data/lib/openc3/io/udp_sockets.rb +38 -8
  182. data/lib/openc3/io/win32_serial_driver.rb +1 -0
  183. data/lib/openc3/logs/buffered_packet_log_reader.rb +0 -0
  184. data/lib/openc3/logs/buffered_packet_log_writer.rb +0 -0
  185. data/lib/openc3/logs/log_writer.rb +43 -7
  186. data/lib/openc3/logs/packet_log_constants.rb +0 -0
  187. data/lib/openc3/logs/packet_log_reader.rb +23 -2
  188. data/lib/openc3/logs/packet_log_writer.rb +6 -2
  189. data/lib/openc3/logs/stream_log.rb +1 -1
  190. data/lib/openc3/logs/stream_log_pair.rb +0 -0
  191. data/lib/openc3/logs/text_log_writer.rb +0 -0
  192. data/lib/openc3/logs.rb +0 -0
  193. data/lib/openc3/microservices/cleanup_microservice.rb +0 -0
  194. data/lib/openc3/microservices/decom_common.rb +0 -0
  195. data/lib/openc3/microservices/decom_microservice.rb +0 -0
  196. data/lib/openc3/microservices/interface_decom_common.rb +2 -1
  197. data/lib/openc3/microservices/interface_microservice.rb +73 -26
  198. data/lib/openc3/microservices/log_microservice.rb +0 -0
  199. data/lib/openc3/microservices/microservice.rb +3 -3
  200. data/lib/openc3/microservices/multi_microservice.rb +1 -1
  201. data/lib/openc3/microservices/periodic_microservice.rb +2 -2
  202. data/lib/openc3/microservices/plugin_microservice.rb +0 -0
  203. data/lib/openc3/microservices/queue_microservice.rb +0 -0
  204. data/lib/openc3/microservices/router_microservice.rb +0 -0
  205. data/lib/openc3/microservices/scope_cleanup_microservice.rb +0 -0
  206. data/lib/openc3/microservices/text_log_microservice.rb +0 -0
  207. data/lib/openc3/migrations/20220420190000_log_stuff.rb +0 -0
  208. data/lib/openc3/migrations/20221202214600_add_target_names.rb +0 -0
  209. data/lib/openc3/migrations/20221210174900_convert_to_multi.rb +0 -0
  210. data/lib/openc3/migrations/20230915000002_no_scope_log_messages.rb +0 -0
  211. data/lib/openc3/migrations/20231022000000_tlm_viewer_config.rb +0 -0
  212. data/lib/openc3/migrations/20241208080000_no_critical_cmd.rb +0 -0
  213. data/lib/openc3/migrations/20241208080001_no_trigger_group.rb +0 -0
  214. data/lib/openc3/migrations/20250108060000_news_feed.rb +0 -0
  215. data/lib/openc3/migrations/20250402000000_periodic_only_default.rb +0 -0
  216. data/lib/openc3/migrations/20260203000000_remove_store_id.rb +0 -0
  217. data/lib/openc3/migrations/20260204000000_remove_decom_reducer.rb +0 -0
  218. data/lib/openc3/migrations/20260701000000_updated_at_to_int.rb +0 -0
  219. data/lib/openc3/migrations/20260704000000_default_bridge.rb +38 -0
  220. data/lib/openc3/migrations/20260731000000_system_health_json.rb +52 -0
  221. data/lib/openc3/models/activity_model.rb +0 -0
  222. data/lib/openc3/models/auth_model.rb +35 -7
  223. data/lib/openc3/models/bridge_model.rb +133 -0
  224. data/lib/openc3/models/config_keyword_model.rb +63 -0
  225. data/lib/openc3/models/cvt_model.rb +5 -5
  226. data/lib/openc3/models/db_sharded_model.rb +0 -0
  227. data/lib/openc3/models/environment_model.rb +0 -0
  228. data/lib/openc3/models/gem_model.rb +13 -10
  229. data/lib/openc3/models/host_interface_microservice_model.rb +114 -0
  230. data/lib/openc3/models/info_model.rb +0 -0
  231. data/lib/openc3/models/interface_model.rb +179 -20
  232. data/lib/openc3/models/interface_status_model.rb +0 -0
  233. data/lib/openc3/models/metadata_model.rb +0 -0
  234. data/lib/openc3/models/metric_model.rb +0 -0
  235. data/lib/openc3/models/microservice_model.rb +26 -18
  236. data/lib/openc3/models/microservice_status_model.rb +0 -0
  237. data/lib/openc3/models/migration_model.rb +0 -0
  238. data/lib/openc3/models/model.rb +0 -0
  239. data/lib/openc3/models/news_model.rb +0 -0
  240. data/lib/openc3/models/note_model.rb +0 -0
  241. data/lib/openc3/models/offline_access_model.rb +0 -0
  242. data/lib/openc3/models/ping_model.rb +0 -0
  243. data/lib/openc3/models/plugin_model.rb +211 -50
  244. data/lib/openc3/models/plugin_store_model.rb +0 -0
  245. data/lib/openc3/models/process_status_model.rb +0 -0
  246. data/lib/openc3/models/python_package_model.rb +211 -15
  247. data/lib/openc3/models/queue_model.rb +0 -0
  248. data/lib/openc3/models/reaction_model.rb +0 -0
  249. data/lib/openc3/models/reingest_job_model.rb +0 -0
  250. data/lib/openc3/models/router_model.rb +0 -0
  251. data/lib/openc3/models/router_status_model.rb +0 -0
  252. data/lib/openc3/models/scope_model.rb +85 -56
  253. data/lib/openc3/models/script_engine_model.rb +0 -0
  254. data/lib/openc3/models/script_status_model.rb +0 -0
  255. data/lib/openc3/models/secret_model.rb +0 -0
  256. data/lib/openc3/models/setting_model.rb +546 -3
  257. data/lib/openc3/models/sorted_model.rb +0 -0
  258. data/lib/openc3/models/stash_model.rb +0 -0
  259. data/lib/openc3/models/target_model.rb +210 -31
  260. data/lib/openc3/models/timeline_model.rb +0 -0
  261. data/lib/openc3/models/tool_config_model.rb +0 -0
  262. data/lib/openc3/models/tool_model.rb +34 -20
  263. data/lib/openc3/models/trigger_group_model.rb +0 -0
  264. data/lib/openc3/models/trigger_model.rb +20 -0
  265. data/lib/openc3/models/widget_model.rb +0 -0
  266. data/lib/openc3/operators/microservice_operator.rb +79 -8
  267. data/lib/openc3/operators/operator.rb +4 -4
  268. data/lib/openc3/packets/binary_accessor.rb +0 -0
  269. data/lib/openc3/packets/command_validator.rb +0 -0
  270. data/lib/openc3/packets/commands.rb +4 -4
  271. data/lib/openc3/packets/json_packet.rb +0 -0
  272. data/lib/openc3/packets/limits.rb +15 -7
  273. data/lib/openc3/packets/limits_response.rb +0 -0
  274. data/lib/openc3/packets/packet.rb +5 -1
  275. data/lib/openc3/packets/packet_config.rb +0 -0
  276. data/lib/openc3/packets/packet_item.rb +0 -0
  277. data/lib/openc3/packets/packet_item_limits.rb +0 -0
  278. data/lib/openc3/packets/parsers/format_string_parser.rb +0 -0
  279. data/lib/openc3/packets/parsers/limits_parser.rb +0 -0
  280. data/lib/openc3/packets/parsers/limits_response_parser.rb +0 -0
  281. data/lib/openc3/packets/parsers/packet_item_parser.rb +0 -0
  282. data/lib/openc3/packets/parsers/packet_parser.rb +0 -0
  283. data/lib/openc3/packets/parsers/processor_parser.rb +0 -0
  284. data/lib/openc3/packets/parsers/state_parser.rb +1 -1
  285. data/lib/openc3/packets/parsers/xtce_converter.rb +183 -103
  286. data/lib/openc3/packets/parsers/xtce_parser.rb +71 -23
  287. data/lib/openc3/packets/structure.rb +9 -3
  288. data/lib/openc3/packets/structure_item.rb +0 -0
  289. data/lib/openc3/packets/telemetry.rb +3 -3
  290. data/lib/openc3/processors/processor.rb +2 -0
  291. data/lib/openc3/processors/statistics_processor.rb +0 -0
  292. data/lib/openc3/processors/watermark_processor.rb +0 -0
  293. data/lib/openc3/processors.rb +0 -0
  294. data/lib/openc3/script/api_shared.rb +0 -0
  295. data/lib/openc3/script/autonomic.rb +12 -12
  296. data/lib/openc3/script/calendar.rb +0 -0
  297. data/lib/openc3/script/commands.rb +1 -1
  298. data/lib/openc3/script/critical_cmd.rb +0 -0
  299. data/lib/openc3/script/exceptions.rb +0 -0
  300. data/lib/openc3/script/extract.rb +20 -25
  301. data/lib/openc3/script/limits.rb +0 -0
  302. data/lib/openc3/script/metadata.rb +0 -0
  303. data/lib/openc3/script/packages.rb +0 -0
  304. data/lib/openc3/script/plugins.rb +0 -0
  305. data/lib/openc3/script/queue.rb +0 -0
  306. data/lib/openc3/script/screen.rb +0 -0
  307. data/lib/openc3/script/script.rb +2 -2
  308. data/lib/openc3/script/script_runner.rb +0 -0
  309. data/lib/openc3/script/storage.rb +4 -4
  310. data/lib/openc3/script/suite.rb +0 -0
  311. data/lib/openc3/script/suite_results.rb +0 -0
  312. data/lib/openc3/script/suite_runner.rb +26 -0
  313. data/lib/openc3/script/tables.rb +0 -0
  314. data/lib/openc3/script/telemetry.rb +9 -3
  315. data/lib/openc3/script/web_socket_api.rb +247 -134
  316. data/lib/openc3/script.rb +0 -0
  317. data/lib/openc3/script_engines/script_engine.rb +0 -0
  318. data/lib/openc3/streams/mqtt_stream.rb +0 -0
  319. data/lib/openc3/streams/serial_stream.rb +0 -0
  320. data/lib/openc3/streams/stream.rb +0 -0
  321. data/lib/openc3/streams/tcpip_client_stream.rb +0 -0
  322. data/lib/openc3/streams/tcpip_socket_stream.rb +0 -0
  323. data/lib/openc3/streams/web_socket_client_stream.rb +0 -0
  324. data/lib/openc3/subpacketizers/subpacketizer.rb +0 -0
  325. data/lib/openc3/system/system.rb +0 -0
  326. data/lib/openc3/system/target.rb +0 -0
  327. data/lib/openc3/system.rb +0 -0
  328. data/lib/openc3/tools/cmd_tlm_server/api.rb +0 -0
  329. data/lib/openc3/tools/cmd_tlm_server/interface_thread.rb +7 -0
  330. data/lib/openc3/tools/table_manager/table.rb +0 -0
  331. data/lib/openc3/tools/table_manager/table_config.rb +0 -0
  332. data/lib/openc3/tools/table_manager/table_item.rb +0 -0
  333. data/lib/openc3/tools/table_manager/table_item_parser.rb +0 -0
  334. data/lib/openc3/tools/table_manager/table_manager_core.rb +0 -0
  335. data/lib/openc3/tools/table_manager/table_parser.rb +0 -0
  336. data/lib/openc3/tools/test_runner/test.rb +0 -0
  337. data/lib/openc3/top_level.rb +1 -1
  338. data/lib/openc3/topics/autonomic_topic.rb +0 -0
  339. data/lib/openc3/topics/calendar_topic.rb +0 -0
  340. data/lib/openc3/topics/command_decom_topic.rb +16 -1
  341. data/lib/openc3/topics/command_topic.rb +0 -0
  342. data/lib/openc3/topics/config_topic.rb +0 -0
  343. data/lib/openc3/topics/decom_interface_topic.rb +2 -1
  344. data/lib/openc3/topics/interface_topic.rb +8 -2
  345. data/lib/openc3/topics/limits_event_topic.rb +1 -1
  346. data/lib/openc3/topics/notebook_topic.rb +0 -0
  347. data/lib/openc3/topics/queue_topic.rb +0 -0
  348. data/lib/openc3/topics/router_topic.rb +6 -1
  349. data/lib/openc3/topics/system_events_topic.rb +0 -0
  350. data/lib/openc3/topics/telemetry_decom_topic.rb +0 -0
  351. data/lib/openc3/topics/telemetry_topic.rb +0 -0
  352. data/lib/openc3/topics/timeline_topic.rb +0 -0
  353. data/lib/openc3/topics/topic.rb +0 -0
  354. data/lib/openc3/utilities/authentication.rb +4 -4
  355. data/lib/openc3/utilities/authorization.rb +0 -0
  356. data/lib/openc3/utilities/aws_bucket.rb +12 -4
  357. data/lib/openc3/utilities/bucket.rb +1 -1
  358. data/lib/openc3/utilities/bucket_file_cache.rb +55 -22
  359. data/lib/openc3/utilities/bucket_require.rb +0 -0
  360. data/lib/openc3/utilities/bucket_utilities.rb +3 -4
  361. data/lib/openc3/utilities/cli_generator.rb +0 -0
  362. data/lib/openc3/utilities/cmd_log.rb +0 -0
  363. data/lib/openc3/utilities/config_overlay.rb +75 -0
  364. data/lib/openc3/utilities/cosmos_rails_formatter.rb +0 -0
  365. data/lib/openc3/utilities/crc.rb +59 -32
  366. data/lib/openc3/utilities/csv.rb +5 -5
  367. data/lib/openc3/utilities/ctrf.rb +0 -0
  368. data/lib/openc3/utilities/env_helper.rb +0 -0
  369. data/lib/openc3/utilities/local_bucket.rb +0 -0
  370. data/lib/openc3/utilities/local_mode.rb +36 -7
  371. data/lib/openc3/utilities/logger.rb +30 -8
  372. data/lib/openc3/utilities/message_log.rb +0 -0
  373. data/lib/openc3/utilities/metric.rb +0 -0
  374. data/lib/openc3/utilities/migration.rb +0 -0
  375. data/lib/openc3/utilities/open_telemetry.rb +0 -0
  376. data/lib/openc3/utilities/process_manager.rb +0 -0
  377. data/lib/openc3/utilities/pypi_url.rb +0 -0
  378. data/lib/openc3/utilities/python_proxy.rb +0 -0
  379. data/lib/openc3/utilities/python_venv.rb +135 -0
  380. data/lib/openc3/utilities/quaternion.rb +0 -0
  381. data/lib/openc3/utilities/questdb_client.rb +0 -0
  382. data/lib/openc3/utilities/redis_secrets.rb +0 -0
  383. data/lib/openc3/utilities/reingest_job.rb +0 -0
  384. data/lib/openc3/utilities/ruby_lex_utils.rb +5 -1
  385. data/lib/openc3/utilities/rubygems_url.rb +39 -0
  386. data/lib/openc3/utilities/running_script.rb +119 -95
  387. data/lib/openc3/utilities/s3_autoload.rb +5 -5
  388. data/lib/openc3/utilities/script.rb +52 -12
  389. data/lib/openc3/utilities/secrets.rb +48 -3
  390. data/lib/openc3/utilities/simulated_target.rb +0 -0
  391. data/lib/openc3/utilities/sleeper.rb +0 -0
  392. data/lib/openc3/utilities/store.rb +0 -0
  393. data/lib/openc3/utilities/store_autoload.rb +2 -2
  394. data/lib/openc3/utilities/store_queued.rb +0 -0
  395. data/lib/openc3/utilities/target_file.rb +18 -1
  396. data/lib/openc3/utilities/thread_manager.rb +0 -0
  397. data/lib/openc3/utilities/throttle.rb +0 -0
  398. data/lib/openc3/utilities/zip.rb +0 -0
  399. data/lib/openc3/utilities.rb +0 -0
  400. data/lib/openc3/version.rb +6 -6
  401. data/lib/openc3/win32/excel.rb +0 -0
  402. data/lib/openc3/win32/win32.rb +0 -0
  403. data/lib/openc3/win32/win32_main.rb +0 -0
  404. data/lib/openc3.rb +2 -2
  405. data/tasks/gemfile_stats.rake +0 -0
  406. data/tasks/spec.rake +0 -0
  407. data/templates/command_validator/command_validator.py +0 -0
  408. data/templates/command_validator/command_validator.rb +0 -0
  409. data/templates/conversion/conversion.py +0 -0
  410. data/templates/conversion/conversion.rb +0 -0
  411. data/templates/limits_response/response.py +0 -0
  412. data/templates/limits_response/response.rb +0 -0
  413. data/templates/microservice/microservices/TEMPLATE/microservice.py +0 -0
  414. data/templates/microservice/microservices/TEMPLATE/microservice.rb +0 -0
  415. data/templates/plugin/.gitignore +0 -0
  416. data/templates/plugin/LICENSE.md +0 -0
  417. data/templates/plugin/README.md +0 -0
  418. data/templates/plugin/Rakefile +0 -0
  419. data/templates/plugin/plugin.gemspec +1 -1
  420. data/templates/plugin/plugin.txt +0 -0
  421. data/templates/processor/processor.py +1 -1
  422. data/templates/processor/processor.rb +0 -0
  423. data/templates/target/targets/TARGET/cmd_tlm/cmd.txt +0 -0
  424. data/templates/target/targets/TARGET/cmd_tlm/tlm.txt +0 -0
  425. data/templates/target/targets/TARGET/lib/target.py +0 -0
  426. data/templates/target/targets/TARGET/lib/target.rb +0 -0
  427. data/templates/target/targets/TARGET/procedures/procedure.py +0 -0
  428. data/templates/target/targets/TARGET/procedures/procedure.rb +0 -0
  429. data/templates/target/targets/TARGET/public/README.txt +0 -0
  430. data/templates/target/targets/TARGET/screens/status.txt +0 -0
  431. data/templates/target/targets/TARGET/target.txt +0 -0
  432. data/templates/tool_angular/.editorconfig +0 -0
  433. data/templates/tool_angular/.gitignore +0 -0
  434. data/templates/tool_angular/.vscode/extensions.json +0 -0
  435. data/templates/tool_angular/.vscode/launch.json +0 -0
  436. data/templates/tool_angular/.vscode/tasks.json +0 -0
  437. data/templates/tool_angular/angular.json +0 -0
  438. data/templates/tool_angular/extra-webpack.config.js +0 -0
  439. data/templates/tool_angular/package.json +2 -2
  440. data/templates/tool_angular/src/app/app-routing.module.ts +0 -0
  441. data/templates/tool_angular/src/app/app.component.html +0 -0
  442. data/templates/tool_angular/src/app/app.component.scss +0 -0
  443. data/templates/tool_angular/src/app/app.component.spec.ts +0 -0
  444. data/templates/tool_angular/src/app/app.component.ts +0 -0
  445. data/templates/tool_angular/src/app/app.module.ts +0 -0
  446. data/templates/tool_angular/src/app/custom-overlay-container.ts +0 -0
  447. data/templates/tool_angular/src/app/empty-route/empty-route.component.ts +0 -0
  448. data/templates/tool_angular/src/app/openc3-api.d.ts +0 -0
  449. data/templates/tool_angular/src/assets/.gitkeep +0 -0
  450. data/templates/tool_angular/src/environments/environment.prod.ts +0 -0
  451. data/templates/tool_angular/src/environments/environment.ts +0 -0
  452. data/templates/tool_angular/src/favicon.ico +0 -0
  453. data/templates/tool_angular/src/index.html +0 -0
  454. data/templates/tool_angular/src/main.single-spa.ts +0 -0
  455. data/templates/tool_angular/src/single-spa/asset-url.ts +0 -0
  456. data/templates/tool_angular/src/single-spa/single-spa-props.ts +0 -0
  457. data/templates/tool_angular/src/styles.scss +0 -0
  458. data/templates/tool_angular/tsconfig.app.json +0 -0
  459. data/templates/tool_angular/tsconfig.json +0 -0
  460. data/templates/tool_angular/tsconfig.spec.json +0 -0
  461. data/templates/tool_react/.eslintrc +0 -0
  462. data/templates/tool_react/.gitignore +0 -0
  463. data/templates/tool_react/.prettierignore +0 -0
  464. data/templates/tool_react/babel.config.json +0 -0
  465. data/templates/tool_react/jest.config.js +0 -0
  466. data/templates/tool_react/package.json +1 -1
  467. data/templates/tool_react/src/openc3-tool_name.js +0 -0
  468. data/templates/tool_react/src/root.component.js +0 -0
  469. data/templates/tool_react/src/root.component.test.js +0 -0
  470. data/templates/tool_react/webpack.config.js +0 -0
  471. data/templates/tool_svelte/.gitignore +0 -0
  472. data/templates/tool_svelte/.prettierignore +0 -0
  473. data/templates/tool_svelte/.prettierrc.js +0 -0
  474. data/templates/tool_svelte/babel.config.js +0 -0
  475. data/templates/tool_svelte/build/smui.css +0 -0
  476. data/templates/tool_svelte/jest.config.js +0 -0
  477. data/templates/tool_svelte/package.json +1 -1
  478. data/templates/tool_svelte/rollup.config.js +0 -0
  479. data/templates/tool_svelte/src/App.svelte +0 -0
  480. data/templates/tool_svelte/src/App.test.js +0 -0
  481. data/templates/tool_svelte/src/theme/_smui-theme.scss +0 -0
  482. data/templates/tool_svelte/src/tool_name.js +0 -0
  483. data/templates/tool_vue/.env.standalone +0 -0
  484. data/templates/tool_vue/.gitignore +0 -0
  485. data/templates/tool_vue/.prettierrc.cjs +0 -0
  486. data/templates/tool_vue/eslint.config.mjs +0 -0
  487. data/templates/tool_vue/jsconfig.json +0 -0
  488. data/templates/tool_vue/package.json +3 -3
  489. data/templates/tool_vue/src/App.vue +0 -0
  490. data/templates/tool_vue/src/main.js +0 -0
  491. data/templates/tool_vue/src/router.js +0 -0
  492. data/templates/tool_vue/src/tools/tool_name/tool_name.vue +0 -0
  493. data/templates/tool_vue/vite.config.js +1 -1
  494. data/templates/widget/.prettierrc.cjs +0 -0
  495. data/templates/widget/LICENSE.md +0 -0
  496. data/templates/widget/Rakefile +0 -0
  497. data/templates/widget/package.json +2 -2
  498. data/templates/widget/src/Widget.vue +0 -0
  499. data/templates/widget/vite.config.js +0 -0
  500. metadata +40 -11
  501. data/templates/tool_vue/.nycrc +0 -3
  502. data/templates/widget/.nycrc +0 -3
@@ -28,6 +28,7 @@ require 'openc3/utilities/script'
28
28
  require 'openc3/utilities/store'
29
29
  require 'openc3/utilities/store_queued'
30
30
  require 'openc3/utilities/bucket_require'
31
+ require 'openc3/utilities/python_venv'
31
32
  require 'openc3/models/offline_access_model'
32
33
  require 'openc3/models/environment_model'
33
34
  require 'openc3/models/script_engine_model'
@@ -36,7 +37,7 @@ require 'fileutils'
36
37
 
37
38
  if not defined? RAILS_ROOT
38
39
  if ENV['RAILS_ROOT']
39
- RAILS_ROOT = ENV['RAILS_ROOT']
40
+ RAILS_ROOT = ENV.fetch('RAILS_ROOT')
40
41
  elsif defined? Rails
41
42
  RAILS_ROOT = Rails.root
42
43
  else
@@ -432,11 +433,11 @@ class RunningScript
432
433
  self.class.message_log
433
434
  end
434
435
 
435
- def self.spawn(scope, name, suite_runner = nil, disconnect = false, environment = nil, user_full_name = nil, username = nil, line_no = nil, end_line_no = nil)
436
+ def self.spawn(scope, name, suite_runner = nil, disconnect = false, environment = nil, user_full_name = nil, username = nil, line_no = nil, end_line_no = nil, python_venv = nil)
436
437
  extension = File.extname(name).to_s.downcase
437
438
  script_engine = nil
438
439
  if extension == '.py'
439
- process_name = ENV['OPENC3_PYTHON_BIN'] || '/openc3/python/.venv/bin/python'
440
+ process_name = ENV.fetch('OPENC3_PYTHON_BIN', '/openc3/python/.venv/bin/python')
440
441
  runner_path = File.join(RAILS_ROOT, 'scripts', 'run_script.py')
441
442
  elsif extension == '.rb'
442
443
  process_name = 'ruby'
@@ -449,7 +450,7 @@ class RunningScript
449
450
  if script_engine_model
450
451
  script_engine = script_engine_model.filename
451
452
  if File.extname(script_engine).to_s.downcase == '.py'
452
- process_name = ENV['OPENC3_PYTHON_BIN'] || '/openc3/python/.venv/bin/python'
453
+ process_name = ENV.fetch('OPENC3_PYTHON_BIN', '/openc3/python/.venv/bin/python')
453
454
  runner_path = File.join(RAILS_ROOT, 'scripts', 'run_script.py')
454
455
  else
455
456
  process_name = 'ruby'
@@ -468,103 +469,126 @@ class RunningScript
468
469
  user_full_name ||= 'Anonymous'
469
470
  start_time = Time.now.utc.iso8601
470
471
 
471
- process = ChildProcess.build(process_name, runner_path.to_s, running_script_id.to_s, scope)
472
- process.io.inherit! # Helps with debugging
473
- script_cwd = File.join(RAILS_ROOT, 'tmp', "script_#{running_script_id}")
474
- FileUtils.mkdir_p(script_cwd)
475
- process.cwd = script_cwd
476
-
477
- # Check for offline access token
478
- model = nil
479
- model = OpenC3::OfflineAccessModel.get_model(name: username, scope: scope) if username != 'Anonymous'
480
-
481
- # Load the global environment variables
482
- status_environment = {}
483
- values = OpenC3::EnvironmentModel.all(scope: scope).values
484
- values.each do |env|
485
- process.environment[env['key']] = env['value']
486
- status_environment[env['key']] = env['value']
487
- end
488
- # Load the script specific ENV vars set by the GUI
489
- # These can override the previously defined global env vars
490
- if environment
491
- environment.each do |env|
472
+ begin
473
+ process = ChildProcess.build(process_name, runner_path.to_s, running_script_id.to_s, scope)
474
+ process.io.inherit! # Helps with debugging
475
+ script_cwd = File.join(RAILS_ROOT, 'tmp', "script_#{running_script_id}")
476
+ FileUtils.mkdir_p(script_cwd)
477
+ process.cwd = script_cwd
478
+
479
+ # Check for offline access token
480
+ model = nil
481
+ model = OpenC3::OfflineAccessModel.get_model(name: username, scope: scope) if username != 'Anonymous'
482
+
483
+ # Load the global environment variables
484
+ status_environment = {}
485
+ values = OpenC3::EnvironmentModel.all(scope: scope).values
486
+ values.each do |env|
492
487
  process.environment[env['key']] = env['value']
493
488
  status_environment[env['key']] = env['value']
494
489
  end
495
- end
490
+ # Load the script specific ENV vars set by the GUI
491
+ # These can override the previously defined global env vars
492
+ if environment
493
+ environment.each do |env|
494
+ process.environment[env['key']] = env['value']
495
+ status_environment[env['key']] = env['value']
496
+ end
497
+ end
496
498
 
497
- script_status = OpenC3::ScriptStatusModel.new(
498
- name: running_script_id.to_s, # Unique id for this script
499
- state: 'spawning', # State will be spawning until the script is running
500
- shard: 0, # Future enhancement of script runner shards
501
- filename: name, # Initial filename never changes
502
- current_filename: name, # Current filename updates while we are running
503
- line_no: 0, # 0 means not running yet
504
- start_line_no: line_no || 1, # Line number to start running the script
505
- end_line_no: end_line_no || nil, # Line number to stop running the script
506
- username: username, # username of the person who started the script
507
- user_full_name: user_full_name, # full name of the person who started the script
508
- start_time: start_time, # Time the script started ISO format
509
- end_time: nil, # Time the script ended ISO format
510
- disconnect: disconnect, # Disconnect is set to true if the script is running in a disconnected mode
511
- environment: status_environment, # hash of environment variables set for the script
512
- suite_runner: suite_runner ? suite_runner : nil, # current suite information if any
513
- errors: nil, # array of errors that occurred during the script run
514
- pid: nil, # pid of the script process - set by the script itself when it starts
515
- script_engine: script_engine, # script engine filename
516
- updated_at: nil, # Set by create/update
517
- scope: scope # Scope of the script
518
- )
519
- script_status.create()
520
-
521
- # Set proper secrets for running script
522
- process.environment['SECRET_KEY_BASE'] = nil
523
- process.environment['OPENC3_REDIS_USERNAME'] = ENV['OPENC3_SR_REDIS_USERNAME']
524
- process.environment['OPENC3_REDIS_PASSWORD'] = ENV['OPENC3_SR_REDIS_PASSWORD']
525
- process.environment['OPENC3_BUCKET_USERNAME'] = ENV['OPENC3_SR_BUCKET_USERNAME']
526
- process.environment['OPENC3_BUCKET_PASSWORD'] = ENV['OPENC3_SR_BUCKET_PASSWORD']
527
- process.environment['OPENC3_SR_REDIS_USERNAME'] = nil
528
- process.environment['OPENC3_SR_REDIS_PASSWORD'] = nil
529
- process.environment['OPENC3_SR_BUCKET_USERNAME'] = nil
530
- process.environment['OPENC3_SR_BUCKET_PASSWORD'] = nil
531
- process.environment['OPENC3_API_CLIENT'] = ENV['OPENC3_API_CLIENT']
532
- if model and model.offline_access_token
533
- auth = OpenC3::OpenC3KeycloakAuthentication.new(ENV['OPENC3_KEYCLOAK_URL'])
534
- valid_token = auth.get_token_from_refresh_token(model.offline_access_token)
535
- if valid_token
536
- process.environment['OPENC3_API_TOKEN'] = model.offline_access_token
499
+ script_status = OpenC3::ScriptStatusModel.new(
500
+ name: running_script_id.to_s, # Unique id for this script
501
+ state: 'spawning', # State will be spawning until the script is running
502
+ shard: 0, # Future enhancement of script runner shards
503
+ filename: name, # Initial filename never changes
504
+ current_filename: name, # Current filename updates while we are running
505
+ line_no: 0, # 0 means not running yet
506
+ start_line_no: line_no || 1, # Line number to start running the script
507
+ end_line_no: end_line_no || nil, # Line number to stop running the script
508
+ username: username, # username of the person who started the script
509
+ user_full_name: user_full_name, # full name of the person who started the script
510
+ start_time: start_time, # Time the script started ISO format
511
+ end_time: nil, # Time the script ended ISO format
512
+ disconnect: disconnect, # Disconnect is set to true if the script is running in a disconnected mode
513
+ environment: status_environment, # hash of environment variables set for the script
514
+ suite_runner: suite_runner ? suite_runner : nil, # current suite information if any
515
+ errors: nil, # array of errors that occurred during the script run
516
+ pid: nil, # pid of the script process - set by the script itself when it starts
517
+ script_engine: script_engine, # script engine filename
518
+ updated_at: nil, # Set by create/update
519
+ scope: scope # Scope of the script
520
+ )
521
+ script_status.create()
522
+
523
+ # Set proper secrets for running script
524
+ process.environment['SECRET_KEY_BASE'] = nil
525
+ process.environment['OPENC3_REDIS_USERNAME'] = ENV.fetch('OPENC3_SR_REDIS_USERNAME', nil)
526
+ process.environment['OPENC3_REDIS_PASSWORD'] = ENV.fetch('OPENC3_SR_REDIS_PASSWORD', nil)
527
+ process.environment['OPENC3_BUCKET_USERNAME'] = ENV.fetch('OPENC3_SR_BUCKET_USERNAME', nil)
528
+ process.environment['OPENC3_BUCKET_PASSWORD'] = ENV.fetch('OPENC3_SR_BUCKET_PASSWORD', nil)
529
+ process.environment['OPENC3_SR_REDIS_USERNAME'] = nil
530
+ process.environment['OPENC3_SR_REDIS_PASSWORD'] = nil
531
+ process.environment['OPENC3_SR_BUCKET_USERNAME'] = nil
532
+ process.environment['OPENC3_SR_BUCKET_PASSWORD'] = nil
533
+ process.environment['OPENC3_API_CLIENT'] = ENV.fetch('OPENC3_API_CLIENT', nil)
534
+ if model and model.offline_access_token
535
+ auth = OpenC3::OpenC3KeycloakAuthentication.new(ENV.fetch('OPENC3_KEYCLOAK_URL', nil))
536
+ valid_token = auth.get_token_from_refresh_token(model.offline_access_token)
537
+ if valid_token
538
+ process.environment['OPENC3_API_TOKEN'] = model.offline_access_token
539
+ else
540
+ model.offline_access_token = nil
541
+ model.update
542
+ raise "offline_access token invalid for script"
543
+ end
537
544
  else
538
- model.offline_access_token = nil
539
- model.update
540
- raise "offline_access token invalid for script"
545
+ process.environment['OPENC3_API_USER'] = ENV.fetch('OPENC3_API_USER', nil)
546
+ if ENV['OPENC3_SERVICE_PASSWORD']
547
+ process.environment['OPENC3_API_PASSWORD'] = ENV.fetch('OPENC3_SERVICE_PASSWORD')
548
+ else
549
+ raise "No authentication available for script"
550
+ end
541
551
  end
542
- else
543
- process.environment['OPENC3_API_USER'] = ENV['OPENC3_API_USER']
544
- if ENV['OPENC3_SERVICE_PASSWORD']
545
- process.environment['OPENC3_API_PASSWORD'] = ENV['OPENC3_SERVICE_PASSWORD']
546
- else
547
- raise "No authentication available for script"
552
+ process.environment['GEM_HOME'] = ENV.fetch('GEM_HOME', nil)
553
+
554
+ python_venv_dir = OpenC3::PythonVenv.configure_for_script(
555
+ process.environment, name: name, scope: scope, python_venv: python_venv
556
+ )
557
+ unless python_venv_dir
558
+ system_venv = File.dirname(ENV.fetch('OPENC3_PYTHON_BIN', '/openc3/python/.venv/bin/python')).chomp('/bin')
559
+ process.environment['VIRTUAL_ENV'] = system_venv
560
+ process.environment['PYTHONUSERBASE'] = ENV.fetch('PYTHONUSERBASE', OpenC3::PythonVenv::DEFAULT_PYTHONUSERBASE)
561
+ # Deliberately nil when PYTHONPATH is unset rather than defaulting to
562
+ # something like '.', which would put the script's own working directory
563
+ # on the import path and let a local file shadow a real module.
564
+ process.environment['PYTHONPATH'] = ENV.fetch('PYTHONPATH', nil)
548
565
  end
549
- end
550
- process.environment['GEM_HOME'] = ENV['GEM_HOME']
551
- process.environment['PYTHONUSERBASE'] = ENV['PYTHONUSERBASE']
552
- # Preserve PYTHONPATH to ensure Python can find both UV venv and user packages
553
- process.environment['PYTHONPATH'] = ENV['PYTHONPATH']
554
-
555
- # Spawned process should not be controlled by same Bundler constraints as spawning process
556
- ENV.each do |key, _value|
557
- if key =~ /^BUNDLE/
558
- process.environment[key] = nil
566
+
567
+ # Spawned process should not be controlled by same Bundler constraints as spawning process
568
+ ENV.each do |key, _value|
569
+ if key =~ /^BUNDLE/
570
+ process.environment[key] = nil
571
+ end
559
572
  end
560
- end
561
- process.environment['RUBYOPT'] = nil # Removes loading bundler setup
562
- process.environment['OPENC3_SCOPE'] = scope
563
- process.environment['RAILS_ROOT'] = RAILS_ROOT
573
+ process.environment['RUBYOPT'] = nil # Removes loading bundler setup
574
+ process.environment['OPENC3_SCOPE'] = scope
575
+ process.environment['RAILS_ROOT'] = RAILS_ROOT
564
576
 
565
- process.detach = true
566
- process.start
567
- running_script_id
577
+ process.detach = true
578
+ process.start
579
+ running_script_id
580
+ rescue => e
581
+ begin
582
+ # Clean up the orphaned script status record so the script
583
+ # does not remain stuck in 'spawning' state forever.
584
+ # Guard against nil in case the error occurred before these were assigned.
585
+ script_status.destroy() if script_status
586
+ FileUtils.rm_rf(script_cwd) if script_cwd
587
+ rescue => cleanup_error
588
+ OpenC3::Logger.error("Failed to cleanup after spawn failure: #{cleanup_error.message}")
589
+ end
590
+ raise e
591
+ end
568
592
  end
569
593
 
570
594
  def initialize(script_status)
@@ -601,7 +625,7 @@ class RunningScript
601
625
 
602
626
  # Retrieve file
603
627
  @body = ::Script.body(@script_status.scope, @script_status.filename)
604
- raise "Script not found: #{@script_status.filename}" if @body.nil?
628
+ raise "Script not found during initialization: #{@script_status.filename}" if @body.nil?
605
629
  breakpoints = @@breakpoints[@script_status.filename]&.filter { |_, present| present }&.map { |line_number, _| line_number - 1 } # -1 because frontend lines are 0-indexed
606
630
  breakpoints ||= []
607
631
  running_script_anycable_publish("running-script-channel:#{@script_status.id}", { type: :file, filename: @script_status.filename, scope: @script_status.scope, text: @body.to_utf8, breakpoints: breakpoints })
@@ -1476,7 +1500,7 @@ class RunningScript
1476
1500
  running_script_anycable_publish("running-script-channel:#{@script_status.id}", { type: :file, filename: filename, text: @body.to_utf8, breakpoints: breakpoints })
1477
1501
  else
1478
1502
  text = ::Script.body(@script_status.scope, filename)
1479
- raise "Script not found: #{filename}" if text.nil?
1503
+ raise "Script not found during load/require: #{filename}" if text.nil?
1480
1504
  @@file_cache[filename] = text
1481
1505
  @body = text
1482
1506
  running_script_anycable_publish("running-script-channel:#{@script_status.id}", { type: :file, filename: filename, text: @body.to_utf8, breakpoints: breakpoints })
@@ -1501,7 +1525,7 @@ class RunningScript
1501
1525
  $stdout = OpenC3::Stdout.instance
1502
1526
  $stderr = OpenC3::Stderr.instance
1503
1527
  OpenC3::Logger.stdout = true
1504
- OpenC3::Logger.level = OpenC3::Logger::INFO
1528
+ OpenC3::Logger.level = OpenC3::Logger.default_level
1505
1529
  end
1506
1530
 
1507
1531
  def output_thread
@@ -3,9 +3,9 @@ require 'aws-sdk-s3'
3
3
  if ENV['OPENC3_CLOUD'] == 'local'
4
4
  Aws.config.update(
5
5
  s3: {
6
- endpoint: ENV['OPENC3_BUCKET_URL'] || (ENV['OPENC3_DEVEL'] ? 'http://127.0.0.1:9000' : 'http://openc3-buckets:9000'),
7
- access_key_id: ENV['OPENC3_BUCKET_USERNAME'],
8
- secret_access_key: ENV['OPENC3_BUCKET_PASSWORD'],
6
+ endpoint: ENV.fetch('OPENC3_BUCKET_URL', nil) || (ENV['OPENC3_DEVEL'] ? 'http://127.0.0.1:9000' : 'http://openc3-buckets:9000'),
7
+ access_key_id: ENV.fetch('OPENC3_BUCKET_USERNAME', nil),
8
+ secret_access_key: ENV.fetch('OPENC3_BUCKET_PASSWORD', nil),
9
9
  force_path_style: true,
10
10
  region: 'us-east-1',
11
11
  # Disable automatic checksum calculation for S3-compatible backends (versitygw)
@@ -20,9 +20,9 @@ if ENV['OPENC3_CLOUD'] == 'local'
20
20
  else # AWS
21
21
  Aws.config.update(
22
22
  s3: {
23
- endpoint: "https://s3.#{ENV['AWS_REGION']}.amazonaws.com",
23
+ endpoint: "https://s3.#{ENV.fetch('AWS_REGION', nil)}.amazonaws.com",
24
24
  force_path_style: true,
25
- region: ENV['AWS_REGION']
25
+ region: ENV.fetch('AWS_REGION', nil)
26
26
  }
27
27
  )
28
28
  end
@@ -17,6 +17,7 @@
17
17
 
18
18
  require 'tempfile'
19
19
  require 'openc3/utilities/target_file'
20
+ require 'openc3/utilities/python_venv'
20
21
  require 'openc3/utilities/running_script'
21
22
  require 'openc3/script/suite'
22
23
  require 'openc3/script/suite_runner'
@@ -25,34 +26,64 @@ require 'openc3/tools/test_runner/test'
25
26
  OpenC3.require_file 'openc3/utilities/store'
26
27
 
27
28
  class Script < OpenC3::TargetFile
29
+ # Script lifecycle states and the transitions allowed from each state
30
+ LIFECYCLE_STATES = %w(development review approved)
31
+ LIFECYCLE_TRANSITIONS = {
32
+ 'development' => %w(review approved),
33
+ 'review' => %w(development approved),
34
+ 'approved' => %w(review development),
35
+ }
36
+
28
37
  def self.all(scope, target = nil)
29
38
  super(scope, nil, target: target) # No path matchers
30
39
  end
31
40
 
32
41
  def self.lock(scope, name, username)
33
- name = name.split('*')[0] # Split '*' that indicates modified
34
- OpenC3::Store.hset("#{scope}__script-locks", name, username)
42
+ OpenC3::Store.hset("#{scope}__script-locks", strip_modified(name), username)
35
43
  end
36
44
 
37
45
  def self.unlock(scope, name)
38
- name = name.split('*')[0] # Split '*' that indicates modified
39
- OpenC3::Store.hdel("#{scope}__script-locks", name)
46
+ OpenC3::Store.hdel("#{scope}__script-locks", strip_modified(name))
40
47
  end
41
48
 
42
49
  def self.locked?(scope, name)
43
- name = name.split('*')[0] # Split '*' that indicates modified
44
- locked_by = OpenC3::Store.hget("#{scope}__script-locks", name)
50
+ locked_by = OpenC3::Store.hget("#{scope}__script-locks", strip_modified(name))
45
51
  locked_by ||= false
46
52
  locked_by
47
53
  end
48
54
 
49
55
  def self.get_breakpoints(scope, name)
50
- breakpoints = OpenC3::Store.hget("#{scope}__script-breakpoints", name.split('*')[0]) # Split '*' that indicates modified
56
+ breakpoints = OpenC3::Store.hget("#{scope}__script-breakpoints", strip_modified(name))
51
57
  return JSON.parse(breakpoints, allow_nan: true, create_additions: true) if breakpoints
52
58
  []
53
59
  end
54
60
 
55
- def self.process_suite(name, contents, new_process: true, username: nil, scope:)
61
+ def self.temp_file?(name)
62
+ strip_modified(name).start_with?("#{TEMP_FOLDER}/")
63
+ end
64
+
65
+ def self.lifecycle_enabled?
66
+ defined?(::VersionStore) && ::VersionStore.enabled?
67
+ end
68
+
69
+ def self.lifecycle(scope, name)
70
+ return { 'state' => 'development', 'history' => [] } if temp_file?(name)
71
+ return { 'state' => 'development', 'history' => [] } unless lifecycle_enabled?
72
+ ::VersionStore.lifecycle(scope: scope, name: strip_modified(name))
73
+ end
74
+
75
+ # current: the caller-known current state; pass it to avoid a redundant git
76
+ # read when the controller has already fetched it for transition validation.
77
+ def self.set_lifecycle(scope, name, state, username, comment, current: nil)
78
+ raise "Cannot set lifecycle on temporary files" if temp_file?(name)
79
+ raise "Script lifecycle requires the version history store" unless lifecycle_enabled?
80
+ name = strip_modified(name)
81
+ current ||= lifecycle(scope, name)['state']
82
+ ::VersionStore.set_lifecycle(scope: scope, name: name, from: current, to: state,
83
+ username: username, comment: comment)
84
+ end
85
+
86
+ def self.process_suite(name, contents, new_process: true, username: nil, scope:, python_venv: nil)
56
87
  python = false
57
88
  python = true if File.extname(name) == '.py'
58
89
 
@@ -119,10 +150,19 @@ class Script < OpenC3::TargetFile
119
150
  end
120
151
  end
121
152
  process.environment['GEM_HOME'] = ENV['GEM_HOME'] || '/gems'
122
- process.environment['PYTHONUSERBASE'] = ENV['PYTHONUSERBASE'] || '/gems/python_packages'
153
+ process.environment['PYTHONUSERBASE'] = ENV['PYTHONUSERBASE'] || OpenC3::PythonVenv::DEFAULT_PYTHONUSERBASE
123
154
  # Preserve PYTHONPATH to ensure Python can find both UV venv and user packages
124
155
  process.environment['PYTHONPATH'] = ENV['PYTHONPATH'] || '.'
125
156
 
157
+ # Suite analysis executes Python in a separate process before the script
158
+ # itself is started. Give that process the same plugin venv visibility as
159
+ # RunningScript so imports used while defining a suite can be resolved.
160
+ if python
161
+ OpenC3::PythonVenv.configure_for_script(
162
+ process.environment, name: name, scope: scope, python_venv: python_venv
163
+ )
164
+ end
165
+
126
166
  # Spawned process should not be controlled by same Bundler constraints as spawning process
127
167
  ENV.each do |key, _value|
128
168
  if key =~ /^BUNDLE/
@@ -189,7 +229,6 @@ class Script < OpenC3::TargetFile
189
229
  def self.delete_temp(scope)
190
230
  files = super(scope)
191
231
  files.each do |name|
192
- # Remove any breakpoints associated with the temp files
193
232
  OpenC3::Store.hdel("#{scope}__script-breakpoints", "#{TEMP_FOLDER}/#{File.basename(name)}")
194
233
  end
195
234
  end
@@ -208,13 +247,14 @@ class Script < OpenC3::TargetFile
208
247
  user_full_name = nil,
209
248
  username = nil,
210
249
  line_no = nil,
211
- end_line_no = nil
250
+ end_line_no = nil,
251
+ python_venv = nil
212
252
  )
213
253
  # Verify the script exists before spawning a run. Without this check a run
214
254
  # is started and the missing file only surfaces as a runtime error inside
215
255
  # the spawned process. Returning nil lets the caller return a 404.
216
256
  return nil unless Script.body(scope, name)
217
- RunningScript.spawn(scope, name, suite_runner, disconnect, environment, user_full_name, username, line_no, end_line_no)
257
+ RunningScript.spawn(scope, name, suite_runner, disconnect, environment, user_full_name, username, line_no, end_line_no, python_venv)
218
258
  end
219
259
 
220
260
  def self.instrumented(filename, text)
@@ -15,13 +15,58 @@ ENV['OPENC3_SECRET_BACKEND'] ||= 'redis'
15
15
 
16
16
  module OpenC3
17
17
  class Secrets
18
+ # Default base directory that FILE type secret paths must reside under.
19
+ # FILE type secrets are written by the operator (or mounted by Kubernetes)
20
+ # and then read back by the microservice, so the path is a destination we
21
+ # control, not an arbitrary file on the host.
22
+ DEFAULT_SECRET_FILE_DIR = '/tmp'
23
+
18
24
  def initialize
19
25
  @local_secrets = {}
20
26
  end
21
27
 
28
+ # Base directory that FILE type secret paths must reside under
29
+ def self.secret_file_dir
30
+ dir = ENV.fetch('OPENC3_SECRET_FILE_DIR', nil)
31
+ dir = DEFAULT_SECRET_FILE_DIR if dir.nil? or dir.empty?
32
+ dir
33
+ end
34
+
35
+ # Validates a FILE type secret path. The path must expand to a location
36
+ # strictly inside Secrets.secret_file_dir. This prevents a plugin
37
+ # configuration such as 'SECRET FILE KEY /root/.ssh/id_rsa' or
38
+ # 'SECRET FILE KEY /tmp/../etc/shadow' from reading or overwriting arbitrary
39
+ # files as the OpenC3 process user.
40
+ #
41
+ # @param path [String] Path from a SECRET FILE definition
42
+ # @return [String] The validated absolute path
43
+ def self.validate_file_path(path)
44
+ raise ArgumentError, "Secret file path must be a String but is a #{path.class}" unless path.is_a?(String)
45
+ raise ArgumentError, "Secret file path must not be blank" if path.strip.empty?
46
+ raise ArgumentError, "Secret file path must not contain a null byte" if path.include?("\x00")
47
+
48
+ # expand_path collapses '..' and '.', expands '~', and makes a relative path
49
+ # absolute, so the comparison below can't be walked out of. This is
50
+ # deliberately a purely lexical check: it does not touch the filesystem, so
51
+ # a path validates identically at plugin install time (cmd-tlm-api) and at
52
+ # write time (openc3-operator), which are separate containers. Symlinks are
53
+ # rejected by the operator when it writes the secret.
54
+ base = File.expand_path(secret_file_dir)
55
+ absolute = File.expand_path(path)
56
+ unless absolute.start_with?(base + File::SEPARATOR)
57
+ raise ArgumentError, "Secret file path '#{path}' must be under '#{base}'"
58
+ end
59
+ absolute
60
+ end
61
+
62
+ # @return true if path is inside base, or is base itself
63
+ def self.path_contained?(base, path)
64
+ path == base or path.start_with?(base.end_with?(File::SEPARATOR) ? base : base + File::SEPARATOR)
65
+ end
66
+
22
67
  def self.getClient
23
68
  raise 'OPENC3_SECRET_BACKEND environment variable is required' unless ENV['OPENC3_SECRET_BACKEND']
24
- secrets_class = ENV['OPENC3_SECRET_BACKEND'].capitalize + 'Secrets'
69
+ secrets_class = ENV.fetch('OPENC3_SECRET_BACKEND').capitalize + 'Secrets'
25
70
  klass = OpenC3.require_class('openc3/utilities/' + secrets_class.class_name_to_filename)
26
71
  klass.new
27
72
  end
@@ -50,9 +95,9 @@ module OpenC3
50
95
  type, key, data, _secret_store = secret
51
96
  case type
52
97
  when 'ENV'
53
- @local_secrets[key] = ENV[data]
98
+ @local_secrets[key] = ENV.fetch(data, nil)
54
99
  when 'FILE'
55
- @local_secrets[key] = File.read(data)
100
+ @local_secrets[key] = File.read(Secrets.validate_file_path(data))
56
101
  else
57
102
  raise "Unknown secret type: #{type}"
58
103
  end
File without changes
File without changes
File without changes
@@ -116,8 +116,8 @@ module OpenC3
116
116
  end
117
117
 
118
118
  def initialize(pool_size = 10, db_shard: 0)
119
- @redis_username = ENV['OPENC3_REDIS_USERNAME']
120
- @redis_key = ENV['OPENC3_REDIS_PASSWORD']
119
+ @redis_username = ENV.fetch('OPENC3_REDIS_USERNAME', nil)
120
+ @redis_key = ENV.fetch('OPENC3_REDIS_PASSWORD', nil)
121
121
  hostname = ENV['OPENC3_REDIS_HOSTNAME'].to_s.gsub("SHARDNUM", db_shard.to_s)
122
122
  @redis_url = "redis://#{hostname}:#{ENV.fetch('OPENC3_REDIS_PORT', 6379)}"
123
123
  @redis_pool = StoreConnectionPool.new(size: pool_size) { build_redis() }
File without changes
@@ -21,6 +21,16 @@ module OpenC3
21
21
  # Matches ScriptRunner.vue const TEMP_FOLDER
22
22
  TEMP_FOLDER = '__TEMP__'
23
23
 
24
+ # self.all appends '*' to a name to mark it as modified on the server. The
25
+ # marker is display-only, so every path in and out of the bucket strips it.
26
+ #
27
+ # Strip only '*' at the end of a file name. '*' is a legal S3 object key character
28
+ # and is allowed by FileOpenSaveDialog's filename charset, so files named that
29
+ # way exist in the field and have to stay addressable.
30
+ def self.strip_modified(name)
31
+ name.sub(/\*$/, '')
32
+ end
33
+
24
34
  def self.all(scope, path_matchers, target: nil)
25
35
  target = target.upcase if target
26
36
 
@@ -89,7 +99,7 @@ module OpenC3
89
99
  end
90
100
 
91
101
  def self.body(scope, name)
92
- name = name.split('*')[0] # Split '*' that indicates modified
102
+ name = strip_modified(name) # Remove '*' that indicates modified
93
103
  # First try opening a potentially modified version by looking for the modified target
94
104
  if ENV['OPENC3_LOCAL_MODE']
95
105
  local_file = OpenC3::LocalMode.open_local_file(name, scope: scope)
@@ -122,6 +132,10 @@ module OpenC3
122
132
 
123
133
  def self.create(scope, name, text, content_type: 'text/plain')
124
134
  return false unless text
135
+ # A trailing '*' here is the display-only modified marker leaking in from a
136
+ # file listing, not a name any caller means to write. Remove it to avoid
137
+ # creating a file with a '*' in the name.
138
+ name = strip_modified(name)
125
139
  if ENV['OPENC3_LOCAL_MODE']
126
140
  OpenC3::LocalMode.put_target_file("#{scope}/targets_modified/#{name}", text, scope: scope)
127
141
  end
@@ -146,6 +160,9 @@ module OpenC3
146
160
  end
147
161
 
148
162
  def self.destroy(scope, name)
163
+ # Match body/create so deleting a name taken straight from a listing
164
+ # removes the file the user actually picked
165
+ name = strip_modified(name)
149
166
  if ENV['OPENC3_LOCAL_MODE']
150
167
  OpenC3::LocalMode.delete_local("#{scope}/targets_modified/#{name}")
151
168
  end
File without changes
File without changes
File without changes
File without changes
@@ -1,14 +1,14 @@
1
1
  # encoding: ascii-8bit
2
2
 
3
- OPENC3_VERSION = '7.2.1'
3
+ OPENC3_VERSION = '7.4.0'
4
4
  module OpenC3
5
5
  module Version
6
6
  MAJOR = '7'
7
- MINOR = '2'
8
- PATCH = '1'
7
+ MINOR = '4'
8
+ PATCH = '0'
9
9
  OTHER = ''
10
- BUILD = 'c0b096796e3070340b34bc4368c21f3972ede7a4'
10
+ BUILD = 'ed0cb44ca3c661eaf2babb77664d0063c955c68c'
11
11
  end
12
- VERSION = '7.2.1'
13
- GEM_VERSION = '7.2.1'
12
+ VERSION = '7.4.0'
13
+ GEM_VERSION = '7.4.0'
14
14
  end
File without changes
File without changes
File without changes
data/lib/openc3.rb CHANGED
@@ -30,9 +30,9 @@ $VERBOSE = saved_verbose
30
30
  # Add OpenC3 bin folder to PATH
31
31
  require 'openc3/core_ext/kernel'
32
32
  if Kernel.is_windows?
33
- ENV['PATH'] = File.join(File.dirname(__FILE__), '../bin') + ';' + ENV['PATH']
33
+ ENV['PATH'] = File.join(File.dirname(__FILE__), '../bin') + ';' + ENV.fetch('PATH')
34
34
  else
35
- ENV['PATH'] = File.join(File.dirname(__FILE__), '../bin') + ':' + ENV['PATH']
35
+ ENV['PATH'] = File.join(File.dirname(__FILE__), '../bin') + ':' + ENV.fetch('PATH')
36
36
  end
37
37
  require 'openc3/ext/platform' if RUBY_ENGINE == 'ruby' and !ENV['OPENC3_NO_EXT']
38
38
  require 'openc3/version'
File without changes
data/tasks/spec.rake CHANGED
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes