openc3 7.3.0 → 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 (499) 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 +138 -0
  8. data/bin/pipuninstall +0 -0
  9. data/bin/uvinstall +10 -3
  10. data/data/config/_array_params.yaml +0 -0
  11. data/data/config/_canvas_values.yaml +0 -0
  12. data/data/config/_graph_params.yaml +0 -0
  13. data/data/config/_id_items.yaml +0 -0
  14. data/data/config/_id_params.yaml +0 -0
  15. data/data/config/_interfaces.yaml +0 -0
  16. data/data/config/_items.yaml +0 -0
  17. data/data/config/_params.yaml +0 -0
  18. data/data/config/command.yaml +0 -0
  19. data/data/config/command_modifiers.yaml +2 -0
  20. data/data/config/command_telemetry.yaml +0 -0
  21. data/data/config/conversions.yaml +0 -0
  22. data/data/config/graph_settings.yaml +0 -0
  23. data/data/config/interface_modifiers.yaml +103 -1
  24. data/data/config/item_modifiers.yaml +0 -0
  25. data/data/config/microservice.yaml +5 -2
  26. data/data/config/param_item_modifiers.yaml +0 -0
  27. data/data/config/parameter_modifiers.yaml +0 -0
  28. data/data/config/plugins.yaml +0 -0
  29. data/data/config/processors.yaml +0 -0
  30. data/data/config/protocols.yaml +0 -0
  31. data/data/config/screen.yaml +0 -0
  32. data/data/config/settings.yaml +0 -0
  33. data/data/config/table_manager.yaml +0 -0
  34. data/data/config/table_parameter_modifiers.yaml +0 -0
  35. data/data/config/target.yaml +0 -0
  36. data/data/config/target_config.yaml +0 -0
  37. data/data/config/telemetry.yaml +0 -0
  38. data/data/config/telemetry_modifiers.yaml +2 -0
  39. data/data/config/tool.yaml +0 -0
  40. data/data/config/unknown.yaml +0 -0
  41. data/data/config/widgets.yaml +6 -0
  42. data/data/xtce_schemas/SpaceSystem_20180204.xsd +0 -0
  43. data/data/xtce_schemas/xml.xsd +0 -0
  44. data/ext/openc3/ext/array/array.c +0 -0
  45. data/ext/openc3/ext/array/extconf.rb +0 -0
  46. data/ext/openc3/ext/buffered_file/buffered_file.c +0 -0
  47. data/ext/openc3/ext/buffered_file/extconf.rb +0 -0
  48. data/ext/openc3/ext/burst_protocol/burst_protocol.c +6 -0
  49. data/ext/openc3/ext/burst_protocol/extconf.rb +0 -0
  50. data/ext/openc3/ext/config_parser/config_parser.c +0 -0
  51. data/ext/openc3/ext/config_parser/extconf.rb +0 -0
  52. data/ext/openc3/ext/crc/crc.c +0 -0
  53. data/ext/openc3/ext/crc/extconf.rb +0 -0
  54. data/ext/openc3/ext/openc3_io/extconf.rb +0 -0
  55. data/ext/openc3/ext/openc3_io/openc3_io.c +0 -0
  56. data/ext/openc3/ext/packet/extconf.rb +0 -0
  57. data/ext/openc3/ext/packet/packet.c +0 -0
  58. data/ext/openc3/ext/platform/extconf.rb +0 -0
  59. data/ext/openc3/ext/platform/platform.c +4 -1
  60. data/ext/openc3/ext/polynomial_conversion/extconf.rb +0 -0
  61. data/ext/openc3/ext/polynomial_conversion/polynomial_conversion.c +0 -0
  62. data/ext/openc3/ext/string/extconf.rb +0 -0
  63. data/ext/openc3/ext/string/string.c +0 -0
  64. data/ext/openc3/ext/structure/structure.c +49 -17
  65. data/ext/openc3/ext/tabbed_plots_config/extconf.rb +0 -0
  66. data/ext/openc3/ext/tabbed_plots_config/tabbed_plots_config.c +0 -0
  67. data/ext/openc3/ext/telemetry/extconf.rb +0 -0
  68. data/ext/openc3/ext/telemetry/telemetry.c +0 -0
  69. data/lib/cosmos.rb +0 -0
  70. data/lib/cosmosc2.rb +0 -0
  71. data/lib/openc3/accessors/accessor.rb +0 -0
  72. data/lib/openc3/accessors/binary_accessor.rb +63 -9
  73. data/lib/openc3/accessors/cbor_accessor.rb +0 -0
  74. data/lib/openc3/accessors/form_accessor.rb +0 -0
  75. data/lib/openc3/accessors/html_accessor.rb +0 -0
  76. data/lib/openc3/accessors/http_accessor.rb +0 -0
  77. data/lib/openc3/accessors/json_accessor.rb +0 -0
  78. data/lib/openc3/accessors/template_accessor.rb +0 -0
  79. data/lib/openc3/accessors/xml_accessor.rb +0 -0
  80. data/lib/openc3/accessors.rb +0 -0
  81. data/lib/openc3/api/README.md +0 -0
  82. data/lib/openc3/api/api.rb +0 -0
  83. data/lib/openc3/api/authorized_api.rb +0 -0
  84. data/lib/openc3/api/calendar_api.rb +0 -0
  85. data/lib/openc3/api/cmd_api.rb +1 -1
  86. data/lib/openc3/api/config_api.rb +0 -0
  87. data/lib/openc3/api/interface_api.rb +0 -0
  88. data/lib/openc3/api/limits_api.rb +0 -0
  89. data/lib/openc3/api/metrics_api.rb +1 -1
  90. data/lib/openc3/api/offline_access_api.rb +1 -1
  91. data/lib/openc3/api/router_api.rb +0 -0
  92. data/lib/openc3/api/settings_api.rb +22 -18
  93. data/lib/openc3/api/stash_api.rb +1 -1
  94. data/lib/openc3/api/target_api.rb +0 -0
  95. data/lib/openc3/api/tlm_api.rb +4 -3
  96. data/lib/openc3/bridge/bridge.rb +0 -0
  97. data/lib/openc3/bridge/bridge_config.rb +0 -0
  98. data/lib/openc3/bridge/bridge_interface_thread.rb +0 -0
  99. data/lib/openc3/bridge/bridge_router_thread.rb +0 -0
  100. data/lib/openc3/ccsds/ccsds_packet.rb +0 -0
  101. data/lib/openc3/ccsds/ccsds_parser.rb +0 -0
  102. data/lib/openc3/config/config_parser.rb +78 -17
  103. data/lib/openc3/config/meta_config_parser.rb +0 -0
  104. data/lib/openc3/conversions/bit_reverse_conversion.rb +0 -0
  105. data/lib/openc3/conversions/conversion.rb +0 -0
  106. data/lib/openc3/conversions/generic_conversion.rb +0 -0
  107. data/lib/openc3/conversions/ip_read_conversion.rb +0 -0
  108. data/lib/openc3/conversions/ip_write_conversion.rb +0 -0
  109. data/lib/openc3/conversions/object_read_conversion.rb +0 -0
  110. data/lib/openc3/conversions/object_write_conversion.rb +0 -0
  111. data/lib/openc3/conversions/packet_time_formatted_conversion.rb +0 -0
  112. data/lib/openc3/conversions/packet_time_seconds_conversion.rb +0 -0
  113. data/lib/openc3/conversions/polynomial_conversion.rb +0 -0
  114. data/lib/openc3/conversions/processor_conversion.rb +0 -0
  115. data/lib/openc3/conversions/received_count_conversion.rb +0 -0
  116. data/lib/openc3/conversions/received_time_formatted_conversion.rb +0 -0
  117. data/lib/openc3/conversions/received_time_seconds_conversion.rb +0 -0
  118. data/lib/openc3/conversions/segmented_polynomial_conversion.rb +0 -0
  119. data/lib/openc3/conversions/unix_time_conversion.rb +0 -0
  120. data/lib/openc3/conversions/unix_time_formatted_conversion.rb +0 -0
  121. data/lib/openc3/conversions/unix_time_seconds_conversion.rb +0 -0
  122. data/lib/openc3/conversions.rb +0 -0
  123. data/lib/openc3/core_ext/array.rb +0 -0
  124. data/lib/openc3/core_ext/binding.rb +0 -0
  125. data/lib/openc3/core_ext/class.rb +0 -0
  126. data/lib/openc3/core_ext/exception.rb +0 -0
  127. data/lib/openc3/core_ext/faraday.rb +0 -0
  128. data/lib/openc3/core_ext/file.rb +0 -0
  129. data/lib/openc3/core_ext/io.rb +0 -0
  130. data/lib/openc3/core_ext/kernel.rb +0 -0
  131. data/lib/openc3/core_ext/math.rb +0 -0
  132. data/lib/openc3/core_ext/matrix.rb +0 -0
  133. data/lib/openc3/core_ext/objectspace.rb +0 -0
  134. data/lib/openc3/core_ext/openc3_io.rb +0 -0
  135. data/lib/openc3/core_ext/range.rb +0 -0
  136. data/lib/openc3/core_ext/socket.rb +0 -0
  137. data/lib/openc3/core_ext/string.rb +0 -0
  138. data/lib/openc3/core_ext/stringio.rb +0 -0
  139. data/lib/openc3/core_ext/tempfile.rb +0 -0
  140. data/lib/openc3/core_ext/time.rb +0 -0
  141. data/lib/openc3/core_ext.rb +0 -0
  142. data/lib/openc3/ext/.keep +0 -0
  143. data/lib/openc3/interfaces/file_interface.rb +0 -0
  144. data/lib/openc3/interfaces/http_client_interface.rb +0 -0
  145. data/lib/openc3/interfaces/http_server_interface.rb +0 -0
  146. data/lib/openc3/interfaces/interface.rb +5 -4
  147. data/lib/openc3/interfaces/mqtt_interface.rb +0 -0
  148. data/lib/openc3/interfaces/mqtt_stream_interface.rb +2 -0
  149. data/lib/openc3/interfaces/protocols/burst_protocol.rb +27 -0
  150. data/lib/openc3/interfaces/protocols/cmd_response_protocol.rb +0 -0
  151. data/lib/openc3/interfaces/protocols/cobs_protocol.rb +0 -0
  152. data/lib/openc3/interfaces/protocols/crc_protocol.rb +0 -0
  153. data/lib/openc3/interfaces/protocols/fixed_protocol.rb +0 -0
  154. data/lib/openc3/interfaces/protocols/ignore_packet_protocol.rb +0 -0
  155. data/lib/openc3/interfaces/protocols/length_protocol.rb +7 -0
  156. data/lib/openc3/interfaces/protocols/preidentified_protocol.rb +7 -0
  157. data/lib/openc3/interfaces/protocols/protocol.rb +4 -4
  158. data/lib/openc3/interfaces/protocols/slip_protocol.rb +0 -0
  159. data/lib/openc3/interfaces/protocols/template_protocol.rb +2 -2
  160. data/lib/openc3/interfaces/protocols/terminated_protocol.rb +0 -0
  161. data/lib/openc3/interfaces/serial_interface.rb +2 -0
  162. data/lib/openc3/interfaces/simulated_target_interface.rb +0 -0
  163. data/lib/openc3/interfaces/stream_interface.rb +0 -0
  164. data/lib/openc3/interfaces/tcpip_client_interface.rb +2 -0
  165. data/lib/openc3/interfaces/tcpip_server_interface.rb +0 -0
  166. data/lib/openc3/interfaces/udp_interface.rb +16 -5
  167. data/lib/openc3/interfaces.rb +0 -0
  168. data/lib/openc3/io/buffered_file.rb +0 -0
  169. data/lib/openc3/io/io_multiplexer.rb +0 -0
  170. data/lib/openc3/io/json_api.rb +3 -2
  171. data/lib/openc3/io/json_api_object.rb +0 -0
  172. data/lib/openc3/io/json_drb.rb +20 -4
  173. data/lib/openc3/io/json_drb_object.rb +0 -0
  174. data/lib/openc3/io/json_drb_rack.rb +0 -0
  175. data/lib/openc3/io/json_rpc.rb +0 -0
  176. data/lib/openc3/io/posix_serial_driver.rb +0 -0
  177. data/lib/openc3/io/serial_driver.rb +0 -0
  178. data/lib/openc3/io/stderr.rb +0 -0
  179. data/lib/openc3/io/stdout.rb +0 -0
  180. data/lib/openc3/io/udp_sockets.rb +38 -8
  181. data/lib/openc3/io/win32_serial_driver.rb +1 -0
  182. data/lib/openc3/logs/buffered_packet_log_reader.rb +0 -0
  183. data/lib/openc3/logs/buffered_packet_log_writer.rb +0 -0
  184. data/lib/openc3/logs/log_writer.rb +0 -0
  185. data/lib/openc3/logs/packet_log_constants.rb +0 -0
  186. data/lib/openc3/logs/packet_log_reader.rb +23 -2
  187. data/lib/openc3/logs/packet_log_writer.rb +5 -0
  188. data/lib/openc3/logs/stream_log.rb +1 -1
  189. data/lib/openc3/logs/stream_log_pair.rb +0 -0
  190. data/lib/openc3/logs/text_log_writer.rb +0 -0
  191. data/lib/openc3/logs.rb +0 -0
  192. data/lib/openc3/microservices/cleanup_microservice.rb +0 -0
  193. data/lib/openc3/microservices/decom_common.rb +0 -0
  194. data/lib/openc3/microservices/decom_microservice.rb +0 -0
  195. data/lib/openc3/microservices/interface_decom_common.rb +2 -1
  196. data/lib/openc3/microservices/interface_microservice.rb +72 -25
  197. data/lib/openc3/microservices/log_microservice.rb +0 -0
  198. data/lib/openc3/microservices/microservice.rb +3 -3
  199. data/lib/openc3/microservices/multi_microservice.rb +1 -1
  200. data/lib/openc3/microservices/periodic_microservice.rb +2 -2
  201. data/lib/openc3/microservices/plugin_microservice.rb +0 -0
  202. data/lib/openc3/microservices/queue_microservice.rb +0 -0
  203. data/lib/openc3/microservices/router_microservice.rb +0 -0
  204. data/lib/openc3/microservices/scope_cleanup_microservice.rb +0 -0
  205. data/lib/openc3/microservices/text_log_microservice.rb +0 -0
  206. data/lib/openc3/migrations/20220420190000_log_stuff.rb +0 -0
  207. data/lib/openc3/migrations/20221202214600_add_target_names.rb +0 -0
  208. data/lib/openc3/migrations/20221210174900_convert_to_multi.rb +0 -0
  209. data/lib/openc3/migrations/20230915000002_no_scope_log_messages.rb +0 -0
  210. data/lib/openc3/migrations/20231022000000_tlm_viewer_config.rb +0 -0
  211. data/lib/openc3/migrations/20241208080000_no_critical_cmd.rb +0 -0
  212. data/lib/openc3/migrations/20241208080001_no_trigger_group.rb +0 -0
  213. data/lib/openc3/migrations/20250108060000_news_feed.rb +0 -0
  214. data/lib/openc3/migrations/20250402000000_periodic_only_default.rb +0 -0
  215. data/lib/openc3/migrations/20260203000000_remove_store_id.rb +0 -0
  216. data/lib/openc3/migrations/20260204000000_remove_decom_reducer.rb +0 -0
  217. data/lib/openc3/migrations/20260701000000_updated_at_to_int.rb +0 -0
  218. data/lib/openc3/migrations/20260704000000_default_bridge.rb +38 -0
  219. data/lib/openc3/migrations/20260731000000_system_health_json.rb +11 -0
  220. data/lib/openc3/models/activity_model.rb +0 -0
  221. data/lib/openc3/models/auth_model.rb +35 -7
  222. data/lib/openc3/models/bridge_model.rb +133 -0
  223. data/lib/openc3/models/config_keyword_model.rb +63 -0
  224. data/lib/openc3/models/cvt_model.rb +0 -0
  225. data/lib/openc3/models/db_sharded_model.rb +0 -0
  226. data/lib/openc3/models/environment_model.rb +0 -0
  227. data/lib/openc3/models/gem_model.rb +12 -9
  228. data/lib/openc3/models/host_interface_microservice_model.rb +114 -0
  229. data/lib/openc3/models/info_model.rb +0 -0
  230. data/lib/openc3/models/interface_model.rb +178 -19
  231. data/lib/openc3/models/interface_status_model.rb +0 -0
  232. data/lib/openc3/models/metadata_model.rb +0 -0
  233. data/lib/openc3/models/metric_model.rb +0 -0
  234. data/lib/openc3/models/microservice_model.rb +9 -22
  235. data/lib/openc3/models/microservice_status_model.rb +0 -0
  236. data/lib/openc3/models/migration_model.rb +0 -0
  237. data/lib/openc3/models/model.rb +0 -0
  238. data/lib/openc3/models/news_model.rb +0 -0
  239. data/lib/openc3/models/note_model.rb +0 -0
  240. data/lib/openc3/models/offline_access_model.rb +0 -0
  241. data/lib/openc3/models/ping_model.rb +0 -0
  242. data/lib/openc3/models/plugin_model.rb +9 -2
  243. data/lib/openc3/models/plugin_store_model.rb +0 -0
  244. data/lib/openc3/models/process_status_model.rb +0 -0
  245. data/lib/openc3/models/python_package_model.rb +4 -4
  246. data/lib/openc3/models/queue_model.rb +0 -0
  247. data/lib/openc3/models/reaction_model.rb +0 -0
  248. data/lib/openc3/models/reingest_job_model.rb +0 -0
  249. data/lib/openc3/models/router_model.rb +0 -0
  250. data/lib/openc3/models/router_status_model.rb +0 -0
  251. data/lib/openc3/models/scope_model.rb +51 -26
  252. data/lib/openc3/models/script_engine_model.rb +0 -0
  253. data/lib/openc3/models/script_status_model.rb +0 -0
  254. data/lib/openc3/models/secret_model.rb +0 -0
  255. data/lib/openc3/models/setting_model.rb +546 -3
  256. data/lib/openc3/models/sorted_model.rb +0 -0
  257. data/lib/openc3/models/stash_model.rb +0 -0
  258. data/lib/openc3/models/target_model.rb +30 -25
  259. data/lib/openc3/models/timeline_model.rb +0 -0
  260. data/lib/openc3/models/tool_config_model.rb +0 -0
  261. data/lib/openc3/models/tool_model.rb +1 -0
  262. data/lib/openc3/models/trigger_group_model.rb +0 -0
  263. data/lib/openc3/models/trigger_model.rb +20 -0
  264. data/lib/openc3/models/widget_model.rb +0 -0
  265. data/lib/openc3/operators/microservice_operator.rb +64 -22
  266. data/lib/openc3/operators/operator.rb +4 -4
  267. data/lib/openc3/packets/binary_accessor.rb +0 -0
  268. data/lib/openc3/packets/command_validator.rb +0 -0
  269. data/lib/openc3/packets/commands.rb +0 -0
  270. data/lib/openc3/packets/json_packet.rb +0 -0
  271. data/lib/openc3/packets/limits.rb +0 -0
  272. data/lib/openc3/packets/limits_response.rb +0 -0
  273. data/lib/openc3/packets/packet.rb +4 -0
  274. data/lib/openc3/packets/packet_config.rb +0 -0
  275. data/lib/openc3/packets/packet_item.rb +0 -0
  276. data/lib/openc3/packets/packet_item_limits.rb +0 -0
  277. data/lib/openc3/packets/parsers/format_string_parser.rb +0 -0
  278. data/lib/openc3/packets/parsers/limits_parser.rb +0 -0
  279. data/lib/openc3/packets/parsers/limits_response_parser.rb +0 -0
  280. data/lib/openc3/packets/parsers/packet_item_parser.rb +0 -0
  281. data/lib/openc3/packets/parsers/packet_parser.rb +0 -0
  282. data/lib/openc3/packets/parsers/processor_parser.rb +0 -0
  283. data/lib/openc3/packets/parsers/state_parser.rb +0 -0
  284. data/lib/openc3/packets/parsers/xtce_converter.rb +0 -0
  285. data/lib/openc3/packets/parsers/xtce_parser.rb +0 -0
  286. data/lib/openc3/packets/structure.rb +7 -1
  287. data/lib/openc3/packets/structure_item.rb +0 -0
  288. data/lib/openc3/packets/telemetry.rb +0 -0
  289. data/lib/openc3/processors/processor.rb +2 -0
  290. data/lib/openc3/processors/statistics_processor.rb +0 -0
  291. data/lib/openc3/processors/watermark_processor.rb +0 -0
  292. data/lib/openc3/processors.rb +0 -0
  293. data/lib/openc3/script/api_shared.rb +0 -0
  294. data/lib/openc3/script/autonomic.rb +12 -12
  295. data/lib/openc3/script/calendar.rb +0 -0
  296. data/lib/openc3/script/commands.rb +0 -0
  297. data/lib/openc3/script/critical_cmd.rb +0 -0
  298. data/lib/openc3/script/exceptions.rb +0 -0
  299. data/lib/openc3/script/extract.rb +0 -0
  300. data/lib/openc3/script/limits.rb +0 -0
  301. data/lib/openc3/script/metadata.rb +0 -0
  302. data/lib/openc3/script/packages.rb +0 -0
  303. data/lib/openc3/script/plugins.rb +0 -0
  304. data/lib/openc3/script/queue.rb +0 -0
  305. data/lib/openc3/script/screen.rb +0 -0
  306. data/lib/openc3/script/script.rb +2 -2
  307. data/lib/openc3/script/script_runner.rb +0 -0
  308. data/lib/openc3/script/storage.rb +4 -4
  309. data/lib/openc3/script/suite.rb +0 -0
  310. data/lib/openc3/script/suite_results.rb +0 -0
  311. data/lib/openc3/script/suite_runner.rb +26 -0
  312. data/lib/openc3/script/tables.rb +0 -0
  313. data/lib/openc3/script/telemetry.rb +9 -3
  314. data/lib/openc3/script/web_socket_api.rb +218 -145
  315. data/lib/openc3/script.rb +0 -0
  316. data/lib/openc3/script_engines/script_engine.rb +0 -0
  317. data/lib/openc3/streams/mqtt_stream.rb +0 -0
  318. data/lib/openc3/streams/serial_stream.rb +0 -0
  319. data/lib/openc3/streams/stream.rb +0 -0
  320. data/lib/openc3/streams/tcpip_client_stream.rb +0 -0
  321. data/lib/openc3/streams/tcpip_socket_stream.rb +0 -0
  322. data/lib/openc3/streams/web_socket_client_stream.rb +0 -0
  323. data/lib/openc3/subpacketizers/subpacketizer.rb +0 -0
  324. data/lib/openc3/system/system.rb +0 -0
  325. data/lib/openc3/system/target.rb +0 -0
  326. data/lib/openc3/system.rb +0 -0
  327. data/lib/openc3/tools/cmd_tlm_server/api.rb +0 -0
  328. data/lib/openc3/tools/cmd_tlm_server/interface_thread.rb +7 -0
  329. data/lib/openc3/tools/table_manager/table.rb +0 -0
  330. data/lib/openc3/tools/table_manager/table_config.rb +0 -0
  331. data/lib/openc3/tools/table_manager/table_item.rb +0 -0
  332. data/lib/openc3/tools/table_manager/table_item_parser.rb +0 -0
  333. data/lib/openc3/tools/table_manager/table_manager_core.rb +0 -0
  334. data/lib/openc3/tools/table_manager/table_parser.rb +0 -0
  335. data/lib/openc3/tools/test_runner/test.rb +0 -0
  336. data/lib/openc3/top_level.rb +1 -1
  337. data/lib/openc3/topics/autonomic_topic.rb +0 -0
  338. data/lib/openc3/topics/calendar_topic.rb +0 -0
  339. data/lib/openc3/topics/command_decom_topic.rb +0 -0
  340. data/lib/openc3/topics/command_topic.rb +0 -0
  341. data/lib/openc3/topics/config_topic.rb +0 -0
  342. data/lib/openc3/topics/decom_interface_topic.rb +2 -1
  343. data/lib/openc3/topics/interface_topic.rb +8 -2
  344. data/lib/openc3/topics/limits_event_topic.rb +0 -0
  345. data/lib/openc3/topics/notebook_topic.rb +0 -0
  346. data/lib/openc3/topics/queue_topic.rb +0 -0
  347. data/lib/openc3/topics/router_topic.rb +6 -1
  348. data/lib/openc3/topics/system_events_topic.rb +0 -0
  349. data/lib/openc3/topics/telemetry_decom_topic.rb +0 -0
  350. data/lib/openc3/topics/telemetry_topic.rb +0 -0
  351. data/lib/openc3/topics/timeline_topic.rb +0 -0
  352. data/lib/openc3/topics/topic.rb +0 -0
  353. data/lib/openc3/utilities/authentication.rb +4 -4
  354. data/lib/openc3/utilities/authorization.rb +0 -0
  355. data/lib/openc3/utilities/aws_bucket.rb +2 -2
  356. data/lib/openc3/utilities/bucket.rb +1 -1
  357. data/lib/openc3/utilities/bucket_file_cache.rb +55 -22
  358. data/lib/openc3/utilities/bucket_require.rb +0 -0
  359. data/lib/openc3/utilities/bucket_utilities.rb +3 -4
  360. data/lib/openc3/utilities/cli_generator.rb +0 -0
  361. data/lib/openc3/utilities/cmd_log.rb +0 -0
  362. data/lib/openc3/utilities/config_overlay.rb +75 -0
  363. data/lib/openc3/utilities/cosmos_rails_formatter.rb +0 -0
  364. data/lib/openc3/utilities/crc.rb +59 -32
  365. data/lib/openc3/utilities/csv.rb +0 -0
  366. data/lib/openc3/utilities/ctrf.rb +0 -0
  367. data/lib/openc3/utilities/env_helper.rb +0 -0
  368. data/lib/openc3/utilities/local_bucket.rb +0 -0
  369. data/lib/openc3/utilities/local_mode.rb +32 -6
  370. data/lib/openc3/utilities/logger.rb +30 -8
  371. data/lib/openc3/utilities/message_log.rb +0 -0
  372. data/lib/openc3/utilities/metric.rb +0 -0
  373. data/lib/openc3/utilities/migration.rb +0 -0
  374. data/lib/openc3/utilities/open_telemetry.rb +0 -0
  375. data/lib/openc3/utilities/process_manager.rb +0 -0
  376. data/lib/openc3/utilities/pypi_url.rb +0 -0
  377. data/lib/openc3/utilities/python_proxy.rb +0 -0
  378. data/lib/openc3/utilities/python_venv.rb +135 -0
  379. data/lib/openc3/utilities/quaternion.rb +0 -0
  380. data/lib/openc3/utilities/questdb_client.rb +0 -0
  381. data/lib/openc3/utilities/redis_secrets.rb +0 -0
  382. data/lib/openc3/utilities/reingest_job.rb +0 -0
  383. data/lib/openc3/utilities/ruby_lex_utils.rb +0 -0
  384. data/lib/openc3/utilities/rubygems_url.rb +39 -0
  385. data/lib/openc3/utilities/running_script.rb +24 -55
  386. data/lib/openc3/utilities/s3_autoload.rb +5 -5
  387. data/lib/openc3/utilities/script.rb +12 -8
  388. data/lib/openc3/utilities/secrets.rb +48 -3
  389. data/lib/openc3/utilities/simulated_target.rb +0 -0
  390. data/lib/openc3/utilities/sleeper.rb +0 -0
  391. data/lib/openc3/utilities/store.rb +0 -0
  392. data/lib/openc3/utilities/store_autoload.rb +2 -2
  393. data/lib/openc3/utilities/store_queued.rb +0 -0
  394. data/lib/openc3/utilities/target_file.rb +18 -1
  395. data/lib/openc3/utilities/thread_manager.rb +0 -0
  396. data/lib/openc3/utilities/throttle.rb +0 -0
  397. data/lib/openc3/utilities/zip.rb +0 -0
  398. data/lib/openc3/utilities.rb +0 -0
  399. data/lib/openc3/version.rb +5 -5
  400. data/lib/openc3/win32/excel.rb +0 -0
  401. data/lib/openc3/win32/win32.rb +0 -0
  402. data/lib/openc3/win32/win32_main.rb +0 -0
  403. data/lib/openc3.rb +2 -2
  404. data/tasks/gemfile_stats.rake +0 -0
  405. data/tasks/spec.rake +0 -0
  406. data/templates/command_validator/command_validator.py +0 -0
  407. data/templates/command_validator/command_validator.rb +0 -0
  408. data/templates/conversion/conversion.py +0 -0
  409. data/templates/conversion/conversion.rb +0 -0
  410. data/templates/limits_response/response.py +0 -0
  411. data/templates/limits_response/response.rb +0 -0
  412. data/templates/microservice/microservices/TEMPLATE/microservice.py +0 -0
  413. data/templates/microservice/microservices/TEMPLATE/microservice.rb +0 -0
  414. data/templates/plugin/.gitignore +0 -0
  415. data/templates/plugin/LICENSE.md +0 -0
  416. data/templates/plugin/README.md +0 -0
  417. data/templates/plugin/Rakefile +0 -0
  418. data/templates/plugin/plugin.gemspec +0 -0
  419. data/templates/plugin/plugin.txt +0 -0
  420. data/templates/processor/processor.py +1 -1
  421. data/templates/processor/processor.rb +0 -0
  422. data/templates/target/targets/TARGET/cmd_tlm/cmd.txt +0 -0
  423. data/templates/target/targets/TARGET/cmd_tlm/tlm.txt +0 -0
  424. data/templates/target/targets/TARGET/lib/target.py +0 -0
  425. data/templates/target/targets/TARGET/lib/target.rb +0 -0
  426. data/templates/target/targets/TARGET/procedures/procedure.py +0 -0
  427. data/templates/target/targets/TARGET/procedures/procedure.rb +0 -0
  428. data/templates/target/targets/TARGET/public/README.txt +0 -0
  429. data/templates/target/targets/TARGET/screens/status.txt +0 -0
  430. data/templates/target/targets/TARGET/target.txt +0 -0
  431. data/templates/tool_angular/.editorconfig +0 -0
  432. data/templates/tool_angular/.gitignore +0 -0
  433. data/templates/tool_angular/.vscode/extensions.json +0 -0
  434. data/templates/tool_angular/.vscode/launch.json +0 -0
  435. data/templates/tool_angular/.vscode/tasks.json +0 -0
  436. data/templates/tool_angular/angular.json +0 -0
  437. data/templates/tool_angular/extra-webpack.config.js +0 -0
  438. data/templates/tool_angular/package.json +2 -2
  439. data/templates/tool_angular/src/app/app-routing.module.ts +0 -0
  440. data/templates/tool_angular/src/app/app.component.html +0 -0
  441. data/templates/tool_angular/src/app/app.component.scss +0 -0
  442. data/templates/tool_angular/src/app/app.component.spec.ts +0 -0
  443. data/templates/tool_angular/src/app/app.component.ts +0 -0
  444. data/templates/tool_angular/src/app/app.module.ts +0 -0
  445. data/templates/tool_angular/src/app/custom-overlay-container.ts +0 -0
  446. data/templates/tool_angular/src/app/empty-route/empty-route.component.ts +0 -0
  447. data/templates/tool_angular/src/app/openc3-api.d.ts +0 -0
  448. data/templates/tool_angular/src/assets/.gitkeep +0 -0
  449. data/templates/tool_angular/src/environments/environment.prod.ts +0 -0
  450. data/templates/tool_angular/src/environments/environment.ts +0 -0
  451. data/templates/tool_angular/src/favicon.ico +0 -0
  452. data/templates/tool_angular/src/index.html +0 -0
  453. data/templates/tool_angular/src/main.single-spa.ts +0 -0
  454. data/templates/tool_angular/src/single-spa/asset-url.ts +0 -0
  455. data/templates/tool_angular/src/single-spa/single-spa-props.ts +0 -0
  456. data/templates/tool_angular/src/styles.scss +0 -0
  457. data/templates/tool_angular/tsconfig.app.json +0 -0
  458. data/templates/tool_angular/tsconfig.json +0 -0
  459. data/templates/tool_angular/tsconfig.spec.json +0 -0
  460. data/templates/tool_react/.eslintrc +0 -0
  461. data/templates/tool_react/.gitignore +0 -0
  462. data/templates/tool_react/.prettierignore +0 -0
  463. data/templates/tool_react/babel.config.json +0 -0
  464. data/templates/tool_react/jest.config.js +0 -0
  465. data/templates/tool_react/package.json +1 -1
  466. data/templates/tool_react/src/openc3-tool_name.js +0 -0
  467. data/templates/tool_react/src/root.component.js +0 -0
  468. data/templates/tool_react/src/root.component.test.js +0 -0
  469. data/templates/tool_react/webpack.config.js +0 -0
  470. data/templates/tool_svelte/.gitignore +0 -0
  471. data/templates/tool_svelte/.prettierignore +0 -0
  472. data/templates/tool_svelte/.prettierrc.js +0 -0
  473. data/templates/tool_svelte/babel.config.js +0 -0
  474. data/templates/tool_svelte/build/smui.css +0 -0
  475. data/templates/tool_svelte/jest.config.js +0 -0
  476. data/templates/tool_svelte/package.json +1 -1
  477. data/templates/tool_svelte/rollup.config.js +0 -0
  478. data/templates/tool_svelte/src/App.svelte +0 -0
  479. data/templates/tool_svelte/src/App.test.js +0 -0
  480. data/templates/tool_svelte/src/theme/_smui-theme.scss +0 -0
  481. data/templates/tool_svelte/src/tool_name.js +0 -0
  482. data/templates/tool_vue/.env.standalone +0 -0
  483. data/templates/tool_vue/.gitignore +0 -0
  484. data/templates/tool_vue/.prettierrc.cjs +0 -0
  485. data/templates/tool_vue/eslint.config.mjs +0 -0
  486. data/templates/tool_vue/jsconfig.json +0 -0
  487. data/templates/tool_vue/package.json +3 -3
  488. data/templates/tool_vue/src/App.vue +0 -0
  489. data/templates/tool_vue/src/main.js +0 -0
  490. data/templates/tool_vue/src/router.js +0 -0
  491. data/templates/tool_vue/src/tools/tool_name/tool_name.vue +0 -0
  492. data/templates/tool_vue/vite.config.js +1 -1
  493. data/templates/widget/.prettierrc.cjs +0 -0
  494. data/templates/widget/LICENSE.md +0 -0
  495. data/templates/widget/Rakefile +0 -0
  496. data/templates/widget/package.json +2 -2
  497. data/templates/widget/src/Widget.vue +0 -0
  498. data/templates/widget/vite.config.js +0 -0
  499. metadata +36 -9
File without changes
File without changes
File without changes
File without changes
@@ -0,0 +1,135 @@
1
+ # encoding: ascii-8bit
2
+
3
+ # Copyright 2026 OpenC3, Inc.
4
+ # All Rights Reserved.
5
+ #
6
+ # This program is distributed in the hope that it will be useful,
7
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
8
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
9
+ # See LICENSE.md for more details.
10
+ #
11
+ # This file may also be used under the terms of a commercial license
12
+ # if purchased from OpenC3, Inc.
13
+
14
+ require 'open3'
15
+ require 'openc3/models/target_model'
16
+ require 'openc3/utilities/target_file'
17
+
18
+ module OpenC3
19
+ module PythonVenv
20
+ PLUGIN_VENVS_DIR = '/gems/plugin_venvs'
21
+
22
+ # Shared user site-packages directory used by plugins that predate the
23
+ # per-plugin UV venvs. Every caller that falls back off a plugin venv points
24
+ # PYTHONUSERBASE here, so keep it in one place rather than repeating the
25
+ # literal in script.rb, running_script.rb, microservice_operator.rb and
26
+ # microservice_model.rb.
27
+ DEFAULT_PYTHONUSERBASE = '/gems/python_packages'
28
+
29
+ # Packages a plugin venv must never supply itself. The scripts and
30
+ # microservices that use a plugin venv still run the base venv's Python
31
+ # binary, and CPython searches every PYTHONPATH entry ahead of the running
32
+ # venv's own site-packages. A plugin that declares openc3 as a dependency
33
+ # would therefore shadow the system library with its own copy, silently
34
+ # swapping the API client the script talks to COSMOS with. Reordering
35
+ # PYTHONPATH cannot fix this - the whole variable outranks the base venv -
36
+ # so the copy is removed at install time instead.
37
+ RESERVED_PACKAGES = ['openc3'].freeze
38
+
39
+ # Resolve a script's plugin venv and expose its packages to a child process.
40
+ # Returns the venv path when configured, or nil when the system environment
41
+ # should be used.
42
+ def self.configure_for_script(environment, name:, scope:, python_venv: nil)
43
+ venv_dir = resolve_script_venv(name: name, scope: scope, python_venv: python_venv)
44
+ return nil unless venv_dir
45
+
46
+ configure_environment(environment, venv_dir)
47
+ venv_dir
48
+ end
49
+
50
+ def self.configure_environment(environment, venv_dir)
51
+ environment['VIRTUAL_ENV'] = venv_dir
52
+ environment['PATH'] = "#{venv_dir}/bin:#{ENV.fetch('PATH', '')}"
53
+ environment['PYTHONUSERBASE'] = venv_dir
54
+
55
+ site_packages = Dir.glob("#{venv_dir}/lib/python*/site-packages").first
56
+ # Prefer a PYTHONPATH the caller already seeded on `environment` over the
57
+ # ambient one. Script.process_suite sets it to ENV['PYTHONPATH'] || '.'
58
+ # before calling here, and reading ENV directly would drop that value.
59
+ existing_pythonpath = environment['PYTHONPATH'] || ENV.fetch('PYTHONPATH', '')
60
+ if site_packages
61
+ environment['PYTHONPATH'] = existing_pythonpath.empty? ? site_packages : "#{site_packages}:#{existing_pythonpath}"
62
+ else
63
+ environment['PYTHONPATH'] = existing_pythonpath.empty? ? nil : existing_pythonpath
64
+ end
65
+
66
+ environment
67
+ end
68
+
69
+ # Remove any RESERVED_PACKAGES a plugin installed into its own venv.
70
+ # Returns the names that were removed so callers can report them.
71
+ def self.purge_reserved_packages(venv_dir)
72
+ return [] if venv_dir.nil? || venv_dir.empty?
73
+
74
+ purged = []
75
+ RESERVED_PACKAGES.each do |package|
76
+ next unless package_installed?(venv_dir, package)
77
+
78
+ Logger.warn("Plugin venv #{venv_dir} declares '#{package}', which would shadow the " \
79
+ "system library. Removing it; the plugin will use the system #{package}.")
80
+ _output, status = Open3.capture2e('uv', 'pip', 'uninstall', '--python', venv_dir, package)
81
+ if status.success?
82
+ purged << package
83
+ else
84
+ Logger.error("Failed to remove '#{package}' from plugin venv #{venv_dir}")
85
+ end
86
+ end
87
+ purged
88
+ rescue => e
89
+ Logger.error("Could not purge reserved packages from #{venv_dir}: #{e.message}")
90
+ []
91
+ end
92
+
93
+ def self.package_installed?(venv_dir, package)
94
+ _output, status = Open3.capture2e('uv', 'pip', 'show', '--python', venv_dir, package)
95
+ status.success?
96
+ end
97
+ private_class_method :package_installed?
98
+
99
+ def self.plugin_venv_path(scope:, plugin_name:)
100
+ sanitized_name = "#{scope}__#{plugin_name}".tr('^a-zA-Z0-9_-', '_')
101
+ candidate = File.join(PLUGIN_VENVS_DIR, sanitized_name, '.venv')
102
+ candidate if File.directory?(candidate)
103
+ end
104
+
105
+ def self.resolve_script_venv(name:, scope:, python_venv: nil)
106
+ target_name = name.split('/')[0].to_s.upcase
107
+ if target_name == TargetFile::TEMP_FOLDER && python_venv
108
+ # File.basename strips directory components; the tr() then whitelists the
109
+ # name down to the character set PluginModel.plugin_venv_name builds it
110
+ # from. That leaves no path separators and no glob metacharacters, so
111
+ # the value cannot escape PLUGIN_VENVS_DIR or widen the Dir.glob in
112
+ # configure_environment.
113
+ safe_name = File.basename(python_venv.to_s).tr('^a-zA-Z0-9_-', '_')
114
+ # The venv name is client-supplied, so it must also be confined to the
115
+ # caller's scope. Venv directories are named "<scope>__<plugin>" by
116
+ # PluginModel.plugin_venv_name; without this check a user in one scope
117
+ # could name another scope's venv and run against its packages.
118
+ return nil unless safe_name.start_with?("#{scope}__".tr('^a-zA-Z0-9_-', '_'))
119
+
120
+ candidate = File.join(PLUGIN_VENVS_DIR, safe_name, '.venv')
121
+ return candidate if File.directory?(candidate)
122
+ else
123
+ target_info = TargetModel.get(name: target_name, scope: scope)
124
+ if target_info && target_info['plugin']
125
+ return plugin_venv_path(scope: scope, plugin_name: target_info['plugin'])
126
+ end
127
+ end
128
+ nil
129
+ rescue => e
130
+ Logger.debug("Could not resolve plugin venv for script '#{name}': #{e.message}")
131
+ nil
132
+ end
133
+ private_class_method :resolve_script_venv
134
+ end
135
+ end
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -0,0 +1,39 @@
1
+ # encoding: ascii-8bit
2
+
3
+ # Copyright 2026 OpenC3, Inc.
4
+ # All Rights Reserved.
5
+ #
6
+ # This program is distributed in the hope that it will be useful,
7
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
8
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
9
+ # See LICENSE.md for more details.
10
+ #
11
+ # This file may also be used under the terms of a commercial license
12
+ # if purchased from OpenC3, Inc.
13
+
14
+ require 'uri'
15
+ require 'openc3/utilities/logger'
16
+
17
+ module OpenC3
18
+ class RubygemsUrl
19
+ DEFAULT = 'https://rubygems.org'
20
+
21
+ # Validate that a resolved rubygems_url is an http(s) URL before it is used
22
+ # as a Gem source. The value can come from a user-writable setting or ENV, so
23
+ # a malformed or non-http value is rejected and replaced with the default
24
+ # rather than passed through to RubyGems.
25
+ #
26
+ # @param rubygems_url [String] the resolved rubygems source url to validate
27
+ # @return [String] the original url if valid, otherwise DEFAULT
28
+ def self.validate(rubygems_url)
29
+ uri = URI.parse(rubygems_url)
30
+ unless uri.is_a?(URI::HTTP) && !uri.host.to_s.empty?
31
+ raise URI::InvalidURIError, "not an http(s) URL"
32
+ end
33
+ rubygems_url
34
+ rescue URI::InvalidURIError => e
35
+ Logger.error("Invalid rubygems_url '#{rubygems_url}' (#{e.message}); falling back to #{DEFAULT}")
36
+ DEFAULT
37
+ end
38
+ end
39
+ end
@@ -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
@@ -436,7 +437,7 @@ class RunningScript
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'
@@ -521,17 +522,17 @@ class RunningScript
521
522
 
522
523
  # Set proper secrets for running script
523
524
  process.environment['SECRET_KEY_BASE'] = nil
524
- process.environment['OPENC3_REDIS_USERNAME'] = ENV['OPENC3_SR_REDIS_USERNAME']
525
- process.environment['OPENC3_REDIS_PASSWORD'] = ENV['OPENC3_SR_REDIS_PASSWORD']
526
- process.environment['OPENC3_BUCKET_USERNAME'] = ENV['OPENC3_SR_BUCKET_USERNAME']
527
- process.environment['OPENC3_BUCKET_PASSWORD'] = ENV['OPENC3_SR_BUCKET_PASSWORD']
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)
528
529
  process.environment['OPENC3_SR_REDIS_USERNAME'] = nil
529
530
  process.environment['OPENC3_SR_REDIS_PASSWORD'] = nil
530
531
  process.environment['OPENC3_SR_BUCKET_USERNAME'] = nil
531
532
  process.environment['OPENC3_SR_BUCKET_PASSWORD'] = nil
532
- process.environment['OPENC3_API_CLIENT'] = ENV['OPENC3_API_CLIENT']
533
+ process.environment['OPENC3_API_CLIENT'] = ENV.fetch('OPENC3_API_CLIENT', nil)
533
534
  if model and model.offline_access_token
534
- auth = OpenC3::OpenC3KeycloakAuthentication.new(ENV['OPENC3_KEYCLOAK_URL'])
535
+ auth = OpenC3::OpenC3KeycloakAuthentication.new(ENV.fetch('OPENC3_KEYCLOAK_URL', nil))
535
536
  valid_token = auth.get_token_from_refresh_token(model.offline_access_token)
536
537
  if valid_token
537
538
  process.environment['OPENC3_API_TOKEN'] = model.offline_access_token
@@ -541,58 +542,26 @@ class RunningScript
541
542
  raise "offline_access token invalid for script"
542
543
  end
543
544
  else
544
- process.environment['OPENC3_API_USER'] = ENV['OPENC3_API_USER']
545
+ process.environment['OPENC3_API_USER'] = ENV.fetch('OPENC3_API_USER', nil)
545
546
  if ENV['OPENC3_SERVICE_PASSWORD']
546
- process.environment['OPENC3_API_PASSWORD'] = ENV['OPENC3_SERVICE_PASSWORD']
547
+ process.environment['OPENC3_API_PASSWORD'] = ENV.fetch('OPENC3_SERVICE_PASSWORD')
547
548
  else
548
549
  raise "No authentication available for script"
549
550
  end
550
551
  end
551
- process.environment['GEM_HOME'] = ENV['GEM_HOME']
552
+ process.environment['GEM_HOME'] = ENV.fetch('GEM_HOME', nil)
552
553
 
553
- # Resolve the per-plugin Python venv for this script. For saved scripts
554
- # we look up which plugin owns the target and check whether that plugin
555
- # has an isolated UV venv. For temp scripts the frontend can pass a
556
- # python_venv name directly, skipping the target lookup entirely.
557
- python_venv_dir = nil
558
- begin
559
- target_name = name.split('/')[0].to_s.upcase
560
- if target_name == '__TEMP__' && python_venv
561
- # File.basename prevents path traversal (strips directory components)
562
- safe_name = File.basename(python_venv.to_s)
563
- candidate = "/gems/plugin_venvs/#{safe_name}/.venv"
564
- python_venv_dir = candidate if File.directory?(candidate)
565
- else
566
- target_info = OpenC3::TargetModel.get(name: target_name, scope: scope)
567
- if target_info && target_info['plugin']
568
- sanitized_name = "#{scope}__#{target_info['plugin']}".tr('^a-zA-Z0-9_-', '_')
569
- candidate = "/gems/plugin_venvs/#{sanitized_name}/.venv"
570
- python_venv_dir = candidate if File.directory?(candidate)
571
- end
572
- end
573
- rescue => e
574
- OpenC3::Logger.debug("Could not resolve plugin venv for script '#{name}': #{e.message}")
575
- end
576
-
577
- if python_venv_dir
578
- process.environment['VIRTUAL_ENV'] = python_venv_dir
579
- process.environment['PATH'] = "#{python_venv_dir}/bin:#{ENV.fetch('PATH', '')}"
580
- process.environment['PYTHONUSERBASE'] = python_venv_dir
581
- # Add plugin venv site-packages to PYTHONPATH so the base venv's Python
582
- # binary can find plugin-specific packages. PYTHONPATH is always respected
583
- # by CPython regardless of venv activation state.
584
- site_packages = Dir.glob("#{python_venv_dir}/lib/python*/site-packages").first
585
- existing_pythonpath = ENV.fetch('PYTHONPATH', '')
586
- if site_packages
587
- process.environment['PYTHONPATH'] = existing_pythonpath.empty? ? site_packages : "#{site_packages}:#{existing_pythonpath}"
588
- else
589
- process.environment['PYTHONPATH'] = existing_pythonpath.empty? ? nil : existing_pythonpath
590
- end
591
- else
592
- system_venv = File.dirname(ENV['OPENC3_PYTHON_BIN'] || '/openc3/python/.venv/bin/python').chomp('/bin')
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')
593
559
  process.environment['VIRTUAL_ENV'] = system_venv
594
- process.environment['PYTHONUSERBASE'] = ENV['PYTHONUSERBASE']
595
- process.environment['PYTHONPATH'] = ENV['PYTHONPATH']
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)
596
565
  end
597
566
 
598
567
  # Spawned process should not be controlled by same Bundler constraints as spawning process
@@ -1556,7 +1525,7 @@ class RunningScript
1556
1525
  $stdout = OpenC3::Stdout.instance
1557
1526
  $stderr = OpenC3::Stderr.instance
1558
1527
  OpenC3::Logger.stdout = true
1559
- OpenC3::Logger.level = OpenC3::Logger::INFO
1528
+ OpenC3::Logger.level = OpenC3::Logger.default_level
1560
1529
  end
1561
1530
 
1562
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'
@@ -37,12 +38,6 @@ class Script < OpenC3::TargetFile
37
38
  super(scope, nil, target: target) # No path matchers
38
39
  end
39
40
 
40
- # Split off the '*' that indicates a file is modified on the server
41
- def self.strip_modified(name)
42
- name.split('*')[0]
43
- end
44
- private_class_method :strip_modified
45
-
46
41
  def self.lock(scope, name, username)
47
42
  OpenC3::Store.hset("#{scope}__script-locks", strip_modified(name), username)
48
43
  end
@@ -88,7 +83,7 @@ class Script < OpenC3::TargetFile
88
83
  username: username, comment: comment)
89
84
  end
90
85
 
91
- def self.process_suite(name, contents, new_process: true, username: nil, scope:)
86
+ def self.process_suite(name, contents, new_process: true, username: nil, scope:, python_venv: nil)
92
87
  python = false
93
88
  python = true if File.extname(name) == '.py'
94
89
 
@@ -155,10 +150,19 @@ class Script < OpenC3::TargetFile
155
150
  end
156
151
  end
157
152
  process.environment['GEM_HOME'] = ENV['GEM_HOME'] || '/gems'
158
- process.environment['PYTHONUSERBASE'] = ENV['PYTHONUSERBASE'] || '/gems/python_packages'
153
+ process.environment['PYTHONUSERBASE'] = ENV['PYTHONUSERBASE'] || OpenC3::PythonVenv::DEFAULT_PYTHONUSERBASE
159
154
  # Preserve PYTHONPATH to ensure Python can find both UV venv and user packages
160
155
  process.environment['PYTHONPATH'] = ENV['PYTHONPATH'] || '.'
161
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
+
162
166
  # Spawned process should not be controlled by same Bundler constraints as spawning process
163
167
  ENV.each do |key, _value|
164
168
  if key =~ /^BUNDLE/
@@ -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.3.0'
3
+ OPENC3_VERSION = '7.4.0'
4
4
  module OpenC3
5
5
  module Version
6
6
  MAJOR = '7'
7
- MINOR = '3'
7
+ MINOR = '4'
8
8
  PATCH = '0'
9
9
  OTHER = ''
10
- BUILD = 'ec6ac203728234fada5e3a2ecab59fba8a94b935'
10
+ BUILD = 'ed0cb44ca3c661eaf2babb77664d0063c955c68c'
11
11
  end
12
- VERSION = '7.3.0'
13
- GEM_VERSION = '7.3.0'
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
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes