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.
- checksums.yaml +4 -4
- data/Gemfile +1 -1
- data/Guardfile +0 -0
- data/LICENSE.md +0 -0
- data/README.md +0 -0
- data/Rakefile +0 -0
- data/bin/openc3cli +138 -0
- data/bin/pipuninstall +0 -0
- data/bin/uvinstall +10 -3
- data/data/config/_array_params.yaml +0 -0
- data/data/config/_canvas_values.yaml +0 -0
- data/data/config/_graph_params.yaml +0 -0
- data/data/config/_id_items.yaml +0 -0
- data/data/config/_id_params.yaml +0 -0
- data/data/config/_interfaces.yaml +0 -0
- data/data/config/_items.yaml +0 -0
- data/data/config/_params.yaml +0 -0
- data/data/config/command.yaml +0 -0
- data/data/config/command_modifiers.yaml +2 -0
- data/data/config/command_telemetry.yaml +0 -0
- data/data/config/conversions.yaml +0 -0
- data/data/config/graph_settings.yaml +0 -0
- data/data/config/interface_modifiers.yaml +103 -1
- data/data/config/item_modifiers.yaml +0 -0
- data/data/config/microservice.yaml +5 -2
- data/data/config/param_item_modifiers.yaml +0 -0
- data/data/config/parameter_modifiers.yaml +0 -0
- data/data/config/plugins.yaml +0 -0
- data/data/config/processors.yaml +0 -0
- data/data/config/protocols.yaml +0 -0
- data/data/config/screen.yaml +0 -0
- data/data/config/settings.yaml +0 -0
- data/data/config/table_manager.yaml +0 -0
- data/data/config/table_parameter_modifiers.yaml +0 -0
- data/data/config/target.yaml +0 -0
- data/data/config/target_config.yaml +0 -0
- data/data/config/telemetry.yaml +0 -0
- data/data/config/telemetry_modifiers.yaml +2 -0
- data/data/config/tool.yaml +0 -0
- data/data/config/unknown.yaml +0 -0
- data/data/config/widgets.yaml +6 -0
- data/data/xtce_schemas/SpaceSystem_20180204.xsd +0 -0
- data/data/xtce_schemas/xml.xsd +0 -0
- data/ext/openc3/ext/array/array.c +0 -0
- data/ext/openc3/ext/array/extconf.rb +0 -0
- data/ext/openc3/ext/buffered_file/buffered_file.c +0 -0
- data/ext/openc3/ext/buffered_file/extconf.rb +0 -0
- data/ext/openc3/ext/burst_protocol/burst_protocol.c +6 -0
- data/ext/openc3/ext/burst_protocol/extconf.rb +0 -0
- data/ext/openc3/ext/config_parser/config_parser.c +0 -0
- data/ext/openc3/ext/config_parser/extconf.rb +0 -0
- data/ext/openc3/ext/crc/crc.c +0 -0
- data/ext/openc3/ext/crc/extconf.rb +0 -0
- data/ext/openc3/ext/openc3_io/extconf.rb +0 -0
- data/ext/openc3/ext/openc3_io/openc3_io.c +0 -0
- data/ext/openc3/ext/packet/extconf.rb +0 -0
- data/ext/openc3/ext/packet/packet.c +0 -0
- data/ext/openc3/ext/platform/extconf.rb +0 -0
- data/ext/openc3/ext/platform/platform.c +4 -1
- data/ext/openc3/ext/polynomial_conversion/extconf.rb +0 -0
- data/ext/openc3/ext/polynomial_conversion/polynomial_conversion.c +0 -0
- data/ext/openc3/ext/string/extconf.rb +0 -0
- data/ext/openc3/ext/string/string.c +0 -0
- data/ext/openc3/ext/structure/structure.c +49 -17
- data/ext/openc3/ext/tabbed_plots_config/extconf.rb +0 -0
- data/ext/openc3/ext/tabbed_plots_config/tabbed_plots_config.c +0 -0
- data/ext/openc3/ext/telemetry/extconf.rb +0 -0
- data/ext/openc3/ext/telemetry/telemetry.c +0 -0
- data/lib/cosmos.rb +0 -0
- data/lib/cosmosc2.rb +0 -0
- data/lib/openc3/accessors/accessor.rb +0 -0
- data/lib/openc3/accessors/binary_accessor.rb +63 -9
- data/lib/openc3/accessors/cbor_accessor.rb +0 -0
- data/lib/openc3/accessors/form_accessor.rb +0 -0
- data/lib/openc3/accessors/html_accessor.rb +0 -0
- data/lib/openc3/accessors/http_accessor.rb +0 -0
- data/lib/openc3/accessors/json_accessor.rb +0 -0
- data/lib/openc3/accessors/template_accessor.rb +0 -0
- data/lib/openc3/accessors/xml_accessor.rb +0 -0
- data/lib/openc3/accessors.rb +0 -0
- data/lib/openc3/api/README.md +0 -0
- data/lib/openc3/api/api.rb +0 -0
- data/lib/openc3/api/authorized_api.rb +0 -0
- data/lib/openc3/api/calendar_api.rb +0 -0
- data/lib/openc3/api/cmd_api.rb +1 -1
- data/lib/openc3/api/config_api.rb +0 -0
- data/lib/openc3/api/interface_api.rb +0 -0
- data/lib/openc3/api/limits_api.rb +0 -0
- data/lib/openc3/api/metrics_api.rb +1 -1
- data/lib/openc3/api/offline_access_api.rb +1 -1
- data/lib/openc3/api/router_api.rb +0 -0
- data/lib/openc3/api/settings_api.rb +22 -18
- data/lib/openc3/api/stash_api.rb +1 -1
- data/lib/openc3/api/target_api.rb +0 -0
- data/lib/openc3/api/tlm_api.rb +4 -3
- data/lib/openc3/bridge/bridge.rb +0 -0
- data/lib/openc3/bridge/bridge_config.rb +0 -0
- data/lib/openc3/bridge/bridge_interface_thread.rb +0 -0
- data/lib/openc3/bridge/bridge_router_thread.rb +0 -0
- data/lib/openc3/ccsds/ccsds_packet.rb +0 -0
- data/lib/openc3/ccsds/ccsds_parser.rb +0 -0
- data/lib/openc3/config/config_parser.rb +78 -17
- data/lib/openc3/config/meta_config_parser.rb +0 -0
- data/lib/openc3/conversions/bit_reverse_conversion.rb +0 -0
- data/lib/openc3/conversions/conversion.rb +0 -0
- data/lib/openc3/conversions/generic_conversion.rb +0 -0
- data/lib/openc3/conversions/ip_read_conversion.rb +0 -0
- data/lib/openc3/conversions/ip_write_conversion.rb +0 -0
- data/lib/openc3/conversions/object_read_conversion.rb +0 -0
- data/lib/openc3/conversions/object_write_conversion.rb +0 -0
- data/lib/openc3/conversions/packet_time_formatted_conversion.rb +0 -0
- data/lib/openc3/conversions/packet_time_seconds_conversion.rb +0 -0
- data/lib/openc3/conversions/polynomial_conversion.rb +0 -0
- data/lib/openc3/conversions/processor_conversion.rb +0 -0
- data/lib/openc3/conversions/received_count_conversion.rb +0 -0
- data/lib/openc3/conversions/received_time_formatted_conversion.rb +0 -0
- data/lib/openc3/conversions/received_time_seconds_conversion.rb +0 -0
- data/lib/openc3/conversions/segmented_polynomial_conversion.rb +0 -0
- data/lib/openc3/conversions/unix_time_conversion.rb +0 -0
- data/lib/openc3/conversions/unix_time_formatted_conversion.rb +0 -0
- data/lib/openc3/conversions/unix_time_seconds_conversion.rb +0 -0
- data/lib/openc3/conversions.rb +0 -0
- data/lib/openc3/core_ext/array.rb +0 -0
- data/lib/openc3/core_ext/binding.rb +0 -0
- data/lib/openc3/core_ext/class.rb +0 -0
- data/lib/openc3/core_ext/exception.rb +0 -0
- data/lib/openc3/core_ext/faraday.rb +0 -0
- data/lib/openc3/core_ext/file.rb +0 -0
- data/lib/openc3/core_ext/io.rb +0 -0
- data/lib/openc3/core_ext/kernel.rb +0 -0
- data/lib/openc3/core_ext/math.rb +0 -0
- data/lib/openc3/core_ext/matrix.rb +0 -0
- data/lib/openc3/core_ext/objectspace.rb +0 -0
- data/lib/openc3/core_ext/openc3_io.rb +0 -0
- data/lib/openc3/core_ext/range.rb +0 -0
- data/lib/openc3/core_ext/socket.rb +0 -0
- data/lib/openc3/core_ext/string.rb +0 -0
- data/lib/openc3/core_ext/stringio.rb +0 -0
- data/lib/openc3/core_ext/tempfile.rb +0 -0
- data/lib/openc3/core_ext/time.rb +0 -0
- data/lib/openc3/core_ext.rb +0 -0
- data/lib/openc3/ext/.keep +0 -0
- data/lib/openc3/interfaces/file_interface.rb +0 -0
- data/lib/openc3/interfaces/http_client_interface.rb +0 -0
- data/lib/openc3/interfaces/http_server_interface.rb +0 -0
- data/lib/openc3/interfaces/interface.rb +5 -4
- data/lib/openc3/interfaces/mqtt_interface.rb +0 -0
- data/lib/openc3/interfaces/mqtt_stream_interface.rb +2 -0
- data/lib/openc3/interfaces/protocols/burst_protocol.rb +27 -0
- data/lib/openc3/interfaces/protocols/cmd_response_protocol.rb +0 -0
- data/lib/openc3/interfaces/protocols/cobs_protocol.rb +0 -0
- data/lib/openc3/interfaces/protocols/crc_protocol.rb +0 -0
- data/lib/openc3/interfaces/protocols/fixed_protocol.rb +0 -0
- data/lib/openc3/interfaces/protocols/ignore_packet_protocol.rb +0 -0
- data/lib/openc3/interfaces/protocols/length_protocol.rb +7 -0
- data/lib/openc3/interfaces/protocols/preidentified_protocol.rb +7 -0
- data/lib/openc3/interfaces/protocols/protocol.rb +4 -4
- data/lib/openc3/interfaces/protocols/slip_protocol.rb +0 -0
- data/lib/openc3/interfaces/protocols/template_protocol.rb +2 -2
- data/lib/openc3/interfaces/protocols/terminated_protocol.rb +0 -0
- data/lib/openc3/interfaces/serial_interface.rb +2 -0
- data/lib/openc3/interfaces/simulated_target_interface.rb +0 -0
- data/lib/openc3/interfaces/stream_interface.rb +0 -0
- data/lib/openc3/interfaces/tcpip_client_interface.rb +2 -0
- data/lib/openc3/interfaces/tcpip_server_interface.rb +0 -0
- data/lib/openc3/interfaces/udp_interface.rb +16 -5
- data/lib/openc3/interfaces.rb +0 -0
- data/lib/openc3/io/buffered_file.rb +0 -0
- data/lib/openc3/io/io_multiplexer.rb +0 -0
- data/lib/openc3/io/json_api.rb +3 -2
- data/lib/openc3/io/json_api_object.rb +0 -0
- data/lib/openc3/io/json_drb.rb +20 -4
- data/lib/openc3/io/json_drb_object.rb +0 -0
- data/lib/openc3/io/json_drb_rack.rb +0 -0
- data/lib/openc3/io/json_rpc.rb +0 -0
- data/lib/openc3/io/posix_serial_driver.rb +0 -0
- data/lib/openc3/io/serial_driver.rb +0 -0
- data/lib/openc3/io/stderr.rb +0 -0
- data/lib/openc3/io/stdout.rb +0 -0
- data/lib/openc3/io/udp_sockets.rb +38 -8
- data/lib/openc3/io/win32_serial_driver.rb +1 -0
- data/lib/openc3/logs/buffered_packet_log_reader.rb +0 -0
- data/lib/openc3/logs/buffered_packet_log_writer.rb +0 -0
- data/lib/openc3/logs/log_writer.rb +0 -0
- data/lib/openc3/logs/packet_log_constants.rb +0 -0
- data/lib/openc3/logs/packet_log_reader.rb +23 -2
- data/lib/openc3/logs/packet_log_writer.rb +5 -0
- data/lib/openc3/logs/stream_log.rb +1 -1
- data/lib/openc3/logs/stream_log_pair.rb +0 -0
- data/lib/openc3/logs/text_log_writer.rb +0 -0
- data/lib/openc3/logs.rb +0 -0
- data/lib/openc3/microservices/cleanup_microservice.rb +0 -0
- data/lib/openc3/microservices/decom_common.rb +0 -0
- data/lib/openc3/microservices/decom_microservice.rb +0 -0
- data/lib/openc3/microservices/interface_decom_common.rb +2 -1
- data/lib/openc3/microservices/interface_microservice.rb +72 -25
- data/lib/openc3/microservices/log_microservice.rb +0 -0
- data/lib/openc3/microservices/microservice.rb +3 -3
- data/lib/openc3/microservices/multi_microservice.rb +1 -1
- data/lib/openc3/microservices/periodic_microservice.rb +2 -2
- data/lib/openc3/microservices/plugin_microservice.rb +0 -0
- data/lib/openc3/microservices/queue_microservice.rb +0 -0
- data/lib/openc3/microservices/router_microservice.rb +0 -0
- data/lib/openc3/microservices/scope_cleanup_microservice.rb +0 -0
- data/lib/openc3/microservices/text_log_microservice.rb +0 -0
- data/lib/openc3/migrations/20220420190000_log_stuff.rb +0 -0
- data/lib/openc3/migrations/20221202214600_add_target_names.rb +0 -0
- data/lib/openc3/migrations/20221210174900_convert_to_multi.rb +0 -0
- data/lib/openc3/migrations/20230915000002_no_scope_log_messages.rb +0 -0
- data/lib/openc3/migrations/20231022000000_tlm_viewer_config.rb +0 -0
- data/lib/openc3/migrations/20241208080000_no_critical_cmd.rb +0 -0
- data/lib/openc3/migrations/20241208080001_no_trigger_group.rb +0 -0
- data/lib/openc3/migrations/20250108060000_news_feed.rb +0 -0
- data/lib/openc3/migrations/20250402000000_periodic_only_default.rb +0 -0
- data/lib/openc3/migrations/20260203000000_remove_store_id.rb +0 -0
- data/lib/openc3/migrations/20260204000000_remove_decom_reducer.rb +0 -0
- data/lib/openc3/migrations/20260701000000_updated_at_to_int.rb +0 -0
- data/lib/openc3/migrations/20260704000000_default_bridge.rb +38 -0
- data/lib/openc3/migrations/20260731000000_system_health_json.rb +11 -0
- data/lib/openc3/models/activity_model.rb +0 -0
- data/lib/openc3/models/auth_model.rb +35 -7
- data/lib/openc3/models/bridge_model.rb +133 -0
- data/lib/openc3/models/config_keyword_model.rb +63 -0
- data/lib/openc3/models/cvt_model.rb +0 -0
- data/lib/openc3/models/db_sharded_model.rb +0 -0
- data/lib/openc3/models/environment_model.rb +0 -0
- data/lib/openc3/models/gem_model.rb +12 -9
- data/lib/openc3/models/host_interface_microservice_model.rb +114 -0
- data/lib/openc3/models/info_model.rb +0 -0
- data/lib/openc3/models/interface_model.rb +178 -19
- data/lib/openc3/models/interface_status_model.rb +0 -0
- data/lib/openc3/models/metadata_model.rb +0 -0
- data/lib/openc3/models/metric_model.rb +0 -0
- data/lib/openc3/models/microservice_model.rb +9 -22
- data/lib/openc3/models/microservice_status_model.rb +0 -0
- data/lib/openc3/models/migration_model.rb +0 -0
- data/lib/openc3/models/model.rb +0 -0
- data/lib/openc3/models/news_model.rb +0 -0
- data/lib/openc3/models/note_model.rb +0 -0
- data/lib/openc3/models/offline_access_model.rb +0 -0
- data/lib/openc3/models/ping_model.rb +0 -0
- data/lib/openc3/models/plugin_model.rb +9 -2
- data/lib/openc3/models/plugin_store_model.rb +0 -0
- data/lib/openc3/models/process_status_model.rb +0 -0
- data/lib/openc3/models/python_package_model.rb +4 -4
- data/lib/openc3/models/queue_model.rb +0 -0
- data/lib/openc3/models/reaction_model.rb +0 -0
- data/lib/openc3/models/reingest_job_model.rb +0 -0
- data/lib/openc3/models/router_model.rb +0 -0
- data/lib/openc3/models/router_status_model.rb +0 -0
- data/lib/openc3/models/scope_model.rb +51 -26
- data/lib/openc3/models/script_engine_model.rb +0 -0
- data/lib/openc3/models/script_status_model.rb +0 -0
- data/lib/openc3/models/secret_model.rb +0 -0
- data/lib/openc3/models/setting_model.rb +546 -3
- data/lib/openc3/models/sorted_model.rb +0 -0
- data/lib/openc3/models/stash_model.rb +0 -0
- data/lib/openc3/models/target_model.rb +30 -25
- data/lib/openc3/models/timeline_model.rb +0 -0
- data/lib/openc3/models/tool_config_model.rb +0 -0
- data/lib/openc3/models/tool_model.rb +1 -0
- data/lib/openc3/models/trigger_group_model.rb +0 -0
- data/lib/openc3/models/trigger_model.rb +20 -0
- data/lib/openc3/models/widget_model.rb +0 -0
- data/lib/openc3/operators/microservice_operator.rb +64 -22
- data/lib/openc3/operators/operator.rb +4 -4
- data/lib/openc3/packets/binary_accessor.rb +0 -0
- data/lib/openc3/packets/command_validator.rb +0 -0
- data/lib/openc3/packets/commands.rb +0 -0
- data/lib/openc3/packets/json_packet.rb +0 -0
- data/lib/openc3/packets/limits.rb +0 -0
- data/lib/openc3/packets/limits_response.rb +0 -0
- data/lib/openc3/packets/packet.rb +4 -0
- data/lib/openc3/packets/packet_config.rb +0 -0
- data/lib/openc3/packets/packet_item.rb +0 -0
- data/lib/openc3/packets/packet_item_limits.rb +0 -0
- data/lib/openc3/packets/parsers/format_string_parser.rb +0 -0
- data/lib/openc3/packets/parsers/limits_parser.rb +0 -0
- data/lib/openc3/packets/parsers/limits_response_parser.rb +0 -0
- data/lib/openc3/packets/parsers/packet_item_parser.rb +0 -0
- data/lib/openc3/packets/parsers/packet_parser.rb +0 -0
- data/lib/openc3/packets/parsers/processor_parser.rb +0 -0
- data/lib/openc3/packets/parsers/state_parser.rb +0 -0
- data/lib/openc3/packets/parsers/xtce_converter.rb +0 -0
- data/lib/openc3/packets/parsers/xtce_parser.rb +0 -0
- data/lib/openc3/packets/structure.rb +7 -1
- data/lib/openc3/packets/structure_item.rb +0 -0
- data/lib/openc3/packets/telemetry.rb +0 -0
- data/lib/openc3/processors/processor.rb +2 -0
- data/lib/openc3/processors/statistics_processor.rb +0 -0
- data/lib/openc3/processors/watermark_processor.rb +0 -0
- data/lib/openc3/processors.rb +0 -0
- data/lib/openc3/script/api_shared.rb +0 -0
- data/lib/openc3/script/autonomic.rb +12 -12
- data/lib/openc3/script/calendar.rb +0 -0
- data/lib/openc3/script/commands.rb +0 -0
- data/lib/openc3/script/critical_cmd.rb +0 -0
- data/lib/openc3/script/exceptions.rb +0 -0
- data/lib/openc3/script/extract.rb +0 -0
- data/lib/openc3/script/limits.rb +0 -0
- data/lib/openc3/script/metadata.rb +0 -0
- data/lib/openc3/script/packages.rb +0 -0
- data/lib/openc3/script/plugins.rb +0 -0
- data/lib/openc3/script/queue.rb +0 -0
- data/lib/openc3/script/screen.rb +0 -0
- data/lib/openc3/script/script.rb +2 -2
- data/lib/openc3/script/script_runner.rb +0 -0
- data/lib/openc3/script/storage.rb +4 -4
- data/lib/openc3/script/suite.rb +0 -0
- data/lib/openc3/script/suite_results.rb +0 -0
- data/lib/openc3/script/suite_runner.rb +26 -0
- data/lib/openc3/script/tables.rb +0 -0
- data/lib/openc3/script/telemetry.rb +9 -3
- data/lib/openc3/script/web_socket_api.rb +218 -145
- data/lib/openc3/script.rb +0 -0
- data/lib/openc3/script_engines/script_engine.rb +0 -0
- data/lib/openc3/streams/mqtt_stream.rb +0 -0
- data/lib/openc3/streams/serial_stream.rb +0 -0
- data/lib/openc3/streams/stream.rb +0 -0
- data/lib/openc3/streams/tcpip_client_stream.rb +0 -0
- data/lib/openc3/streams/tcpip_socket_stream.rb +0 -0
- data/lib/openc3/streams/web_socket_client_stream.rb +0 -0
- data/lib/openc3/subpacketizers/subpacketizer.rb +0 -0
- data/lib/openc3/system/system.rb +0 -0
- data/lib/openc3/system/target.rb +0 -0
- data/lib/openc3/system.rb +0 -0
- data/lib/openc3/tools/cmd_tlm_server/api.rb +0 -0
- data/lib/openc3/tools/cmd_tlm_server/interface_thread.rb +7 -0
- data/lib/openc3/tools/table_manager/table.rb +0 -0
- data/lib/openc3/tools/table_manager/table_config.rb +0 -0
- data/lib/openc3/tools/table_manager/table_item.rb +0 -0
- data/lib/openc3/tools/table_manager/table_item_parser.rb +0 -0
- data/lib/openc3/tools/table_manager/table_manager_core.rb +0 -0
- data/lib/openc3/tools/table_manager/table_parser.rb +0 -0
- data/lib/openc3/tools/test_runner/test.rb +0 -0
- data/lib/openc3/top_level.rb +1 -1
- data/lib/openc3/topics/autonomic_topic.rb +0 -0
- data/lib/openc3/topics/calendar_topic.rb +0 -0
- data/lib/openc3/topics/command_decom_topic.rb +0 -0
- data/lib/openc3/topics/command_topic.rb +0 -0
- data/lib/openc3/topics/config_topic.rb +0 -0
- data/lib/openc3/topics/decom_interface_topic.rb +2 -1
- data/lib/openc3/topics/interface_topic.rb +8 -2
- data/lib/openc3/topics/limits_event_topic.rb +0 -0
- data/lib/openc3/topics/notebook_topic.rb +0 -0
- data/lib/openc3/topics/queue_topic.rb +0 -0
- data/lib/openc3/topics/router_topic.rb +6 -1
- data/lib/openc3/topics/system_events_topic.rb +0 -0
- data/lib/openc3/topics/telemetry_decom_topic.rb +0 -0
- data/lib/openc3/topics/telemetry_topic.rb +0 -0
- data/lib/openc3/topics/timeline_topic.rb +0 -0
- data/lib/openc3/topics/topic.rb +0 -0
- data/lib/openc3/utilities/authentication.rb +4 -4
- data/lib/openc3/utilities/authorization.rb +0 -0
- data/lib/openc3/utilities/aws_bucket.rb +2 -2
- data/lib/openc3/utilities/bucket.rb +1 -1
- data/lib/openc3/utilities/bucket_file_cache.rb +55 -22
- data/lib/openc3/utilities/bucket_require.rb +0 -0
- data/lib/openc3/utilities/bucket_utilities.rb +3 -4
- data/lib/openc3/utilities/cli_generator.rb +0 -0
- data/lib/openc3/utilities/cmd_log.rb +0 -0
- data/lib/openc3/utilities/config_overlay.rb +75 -0
- data/lib/openc3/utilities/cosmos_rails_formatter.rb +0 -0
- data/lib/openc3/utilities/crc.rb +59 -32
- data/lib/openc3/utilities/csv.rb +0 -0
- data/lib/openc3/utilities/ctrf.rb +0 -0
- data/lib/openc3/utilities/env_helper.rb +0 -0
- data/lib/openc3/utilities/local_bucket.rb +0 -0
- data/lib/openc3/utilities/local_mode.rb +32 -6
- data/lib/openc3/utilities/logger.rb +30 -8
- data/lib/openc3/utilities/message_log.rb +0 -0
- data/lib/openc3/utilities/metric.rb +0 -0
- data/lib/openc3/utilities/migration.rb +0 -0
- data/lib/openc3/utilities/open_telemetry.rb +0 -0
- data/lib/openc3/utilities/process_manager.rb +0 -0
- data/lib/openc3/utilities/pypi_url.rb +0 -0
- data/lib/openc3/utilities/python_proxy.rb +0 -0
- data/lib/openc3/utilities/python_venv.rb +135 -0
- data/lib/openc3/utilities/quaternion.rb +0 -0
- data/lib/openc3/utilities/questdb_client.rb +0 -0
- data/lib/openc3/utilities/redis_secrets.rb +0 -0
- data/lib/openc3/utilities/reingest_job.rb +0 -0
- data/lib/openc3/utilities/ruby_lex_utils.rb +0 -0
- data/lib/openc3/utilities/rubygems_url.rb +39 -0
- data/lib/openc3/utilities/running_script.rb +24 -55
- data/lib/openc3/utilities/s3_autoload.rb +5 -5
- data/lib/openc3/utilities/script.rb +12 -8
- data/lib/openc3/utilities/secrets.rb +48 -3
- data/lib/openc3/utilities/simulated_target.rb +0 -0
- data/lib/openc3/utilities/sleeper.rb +0 -0
- data/lib/openc3/utilities/store.rb +0 -0
- data/lib/openc3/utilities/store_autoload.rb +2 -2
- data/lib/openc3/utilities/store_queued.rb +0 -0
- data/lib/openc3/utilities/target_file.rb +18 -1
- data/lib/openc3/utilities/thread_manager.rb +0 -0
- data/lib/openc3/utilities/throttle.rb +0 -0
- data/lib/openc3/utilities/zip.rb +0 -0
- data/lib/openc3/utilities.rb +0 -0
- data/lib/openc3/version.rb +5 -5
- data/lib/openc3/win32/excel.rb +0 -0
- data/lib/openc3/win32/win32.rb +0 -0
- data/lib/openc3/win32/win32_main.rb +0 -0
- data/lib/openc3.rb +2 -2
- data/tasks/gemfile_stats.rake +0 -0
- data/tasks/spec.rake +0 -0
- data/templates/command_validator/command_validator.py +0 -0
- data/templates/command_validator/command_validator.rb +0 -0
- data/templates/conversion/conversion.py +0 -0
- data/templates/conversion/conversion.rb +0 -0
- data/templates/limits_response/response.py +0 -0
- data/templates/limits_response/response.rb +0 -0
- data/templates/microservice/microservices/TEMPLATE/microservice.py +0 -0
- data/templates/microservice/microservices/TEMPLATE/microservice.rb +0 -0
- data/templates/plugin/.gitignore +0 -0
- data/templates/plugin/LICENSE.md +0 -0
- data/templates/plugin/README.md +0 -0
- data/templates/plugin/Rakefile +0 -0
- data/templates/plugin/plugin.gemspec +0 -0
- data/templates/plugin/plugin.txt +0 -0
- data/templates/processor/processor.py +1 -1
- data/templates/processor/processor.rb +0 -0
- data/templates/target/targets/TARGET/cmd_tlm/cmd.txt +0 -0
- data/templates/target/targets/TARGET/cmd_tlm/tlm.txt +0 -0
- data/templates/target/targets/TARGET/lib/target.py +0 -0
- data/templates/target/targets/TARGET/lib/target.rb +0 -0
- data/templates/target/targets/TARGET/procedures/procedure.py +0 -0
- data/templates/target/targets/TARGET/procedures/procedure.rb +0 -0
- data/templates/target/targets/TARGET/public/README.txt +0 -0
- data/templates/target/targets/TARGET/screens/status.txt +0 -0
- data/templates/target/targets/TARGET/target.txt +0 -0
- data/templates/tool_angular/.editorconfig +0 -0
- data/templates/tool_angular/.gitignore +0 -0
- data/templates/tool_angular/.vscode/extensions.json +0 -0
- data/templates/tool_angular/.vscode/launch.json +0 -0
- data/templates/tool_angular/.vscode/tasks.json +0 -0
- data/templates/tool_angular/angular.json +0 -0
- data/templates/tool_angular/extra-webpack.config.js +0 -0
- data/templates/tool_angular/package.json +2 -2
- data/templates/tool_angular/src/app/app-routing.module.ts +0 -0
- data/templates/tool_angular/src/app/app.component.html +0 -0
- data/templates/tool_angular/src/app/app.component.scss +0 -0
- data/templates/tool_angular/src/app/app.component.spec.ts +0 -0
- data/templates/tool_angular/src/app/app.component.ts +0 -0
- data/templates/tool_angular/src/app/app.module.ts +0 -0
- data/templates/tool_angular/src/app/custom-overlay-container.ts +0 -0
- data/templates/tool_angular/src/app/empty-route/empty-route.component.ts +0 -0
- data/templates/tool_angular/src/app/openc3-api.d.ts +0 -0
- data/templates/tool_angular/src/assets/.gitkeep +0 -0
- data/templates/tool_angular/src/environments/environment.prod.ts +0 -0
- data/templates/tool_angular/src/environments/environment.ts +0 -0
- data/templates/tool_angular/src/favicon.ico +0 -0
- data/templates/tool_angular/src/index.html +0 -0
- data/templates/tool_angular/src/main.single-spa.ts +0 -0
- data/templates/tool_angular/src/single-spa/asset-url.ts +0 -0
- data/templates/tool_angular/src/single-spa/single-spa-props.ts +0 -0
- data/templates/tool_angular/src/styles.scss +0 -0
- data/templates/tool_angular/tsconfig.app.json +0 -0
- data/templates/tool_angular/tsconfig.json +0 -0
- data/templates/tool_angular/tsconfig.spec.json +0 -0
- data/templates/tool_react/.eslintrc +0 -0
- data/templates/tool_react/.gitignore +0 -0
- data/templates/tool_react/.prettierignore +0 -0
- data/templates/tool_react/babel.config.json +0 -0
- data/templates/tool_react/jest.config.js +0 -0
- data/templates/tool_react/package.json +1 -1
- data/templates/tool_react/src/openc3-tool_name.js +0 -0
- data/templates/tool_react/src/root.component.js +0 -0
- data/templates/tool_react/src/root.component.test.js +0 -0
- data/templates/tool_react/webpack.config.js +0 -0
- data/templates/tool_svelte/.gitignore +0 -0
- data/templates/tool_svelte/.prettierignore +0 -0
- data/templates/tool_svelte/.prettierrc.js +0 -0
- data/templates/tool_svelte/babel.config.js +0 -0
- data/templates/tool_svelte/build/smui.css +0 -0
- data/templates/tool_svelte/jest.config.js +0 -0
- data/templates/tool_svelte/package.json +1 -1
- data/templates/tool_svelte/rollup.config.js +0 -0
- data/templates/tool_svelte/src/App.svelte +0 -0
- data/templates/tool_svelte/src/App.test.js +0 -0
- data/templates/tool_svelte/src/theme/_smui-theme.scss +0 -0
- data/templates/tool_svelte/src/tool_name.js +0 -0
- data/templates/tool_vue/.env.standalone +0 -0
- data/templates/tool_vue/.gitignore +0 -0
- data/templates/tool_vue/.prettierrc.cjs +0 -0
- data/templates/tool_vue/eslint.config.mjs +0 -0
- data/templates/tool_vue/jsconfig.json +0 -0
- data/templates/tool_vue/package.json +3 -3
- data/templates/tool_vue/src/App.vue +0 -0
- data/templates/tool_vue/src/main.js +0 -0
- data/templates/tool_vue/src/router.js +0 -0
- data/templates/tool_vue/src/tools/tool_name/tool_name.vue +0 -0
- data/templates/tool_vue/vite.config.js +1 -1
- data/templates/widget/.prettierrc.cjs +0 -0
- data/templates/widget/LICENSE.md +0 -0
- data/templates/widget/Rakefile +0 -0
- data/templates/widget/package.json +2 -2
- data/templates/widget/src/Widget.vue +0 -0
- data/templates/widget/vite.config.js +0 -0
- metadata +36 -9
|
@@ -15,27 +15,570 @@
|
|
|
15
15
|
# This file may also be used under the terms of a commercial license
|
|
16
16
|
# if purchased from OpenC3, Inc.
|
|
17
17
|
|
|
18
|
+
require 'json'
|
|
19
|
+
require 'openc3/config/config_parser'
|
|
18
20
|
require 'openc3/models/model'
|
|
19
21
|
|
|
20
22
|
module OpenC3
|
|
21
23
|
class SettingModel < Model
|
|
22
24
|
PRIMARY_KEY = 'openc3__settings'
|
|
23
25
|
|
|
26
|
+
# One environment variable per setting: OPENC3_SETTING_TIME_ZONE=UTC sets
|
|
27
|
+
# the 'time_zone' setting. A prefix scan rather than an enumerated list, so
|
|
28
|
+
# a setting added by a later release needs no change here. Deliberately not
|
|
29
|
+
# a single JSON blob variable: embedding JSON in a compose environment
|
|
30
|
+
# list, a Helm value and an ECS task definition each need different
|
|
31
|
+
# escaping, and one variable per setting reads the same in all three.
|
|
32
|
+
SETTING_ENV_PREFIX = 'OPENC3_SETTING_'
|
|
33
|
+
|
|
34
|
+
# Set to true to write the values on every init rather than only when the
|
|
35
|
+
# setting is missing. Use when the environment is the source of truth and
|
|
36
|
+
# Admin Console edits should not survive a restart.
|
|
37
|
+
#
|
|
38
|
+
# Unlike the OPENC3_NO_* install flags, this is NOT enabled by presence:
|
|
39
|
+
# it discards whatever an operator configured in the Admin Console, so
|
|
40
|
+
# OPENC3_SETTINGS_OVERWRITE=false and =0 mean off, as they read. An
|
|
41
|
+
# unrecognized value is an error rather than a silent guess.
|
|
42
|
+
#
|
|
43
|
+
# Note this does not start with SETTING_ENV_PREFIX, so the prefix scan
|
|
44
|
+
# can't mistake it for a setting named 'overwrite'.
|
|
45
|
+
OVERWRITE_ENV_VAR = 'OPENC3_SETTINGS_OVERWRITE'
|
|
46
|
+
|
|
47
|
+
# Set to true to allow setting names that are not in KNOWN_SETTINGS.
|
|
48
|
+
# Needed for a setting added by a newer tool than this library knows about;
|
|
49
|
+
# without it a misspelled name is reported and skipped.
|
|
50
|
+
ALLOW_UNKNOWN_ENV_VAR = 'OPENC3_SETTINGS_ALLOW_UNKNOWN'
|
|
51
|
+
|
|
52
|
+
# Set to true to make a rejected setting fail init instead of being
|
|
53
|
+
# reported and skipped. Off by default: the init container restarts on
|
|
54
|
+
# failure, so a typo in a cosmetic setting would otherwise crash loop COSMOS
|
|
55
|
+
# with the cause buried in restarting container logs, and the operator ends
|
|
56
|
+
# up with no COSMOS rather than COSMOS with one default time zone. Turn it on
|
|
57
|
+
# for a deployment that would rather not come up than come up misconfigured.
|
|
58
|
+
#
|
|
59
|
+
# `initsettings --dry-run` fails regardless - a preflight check exists to.
|
|
60
|
+
STRICT_ENV_VAR = 'OPENC3_SETTINGS_STRICT'
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
# Every setting that can be seeded from the environment.
|
|
64
|
+
#
|
|
65
|
+
# An unknown name is reported and skipped rather than written: nothing reads
|
|
66
|
+
# it, so the result of a typo is a dead Redis key plus a setting the
|
|
67
|
+
# operator believes they configured and did not. Init still continues -
|
|
68
|
+
# ALLOW_UNKNOWN_ENV_VAR opts out of the check entirely.
|
|
69
|
+
#
|
|
70
|
+
# TO ADD A SETTING, add a row here. The name is the string the Admin
|
|
71
|
+
# Console component passes to loadSetting/saveSetting - find it in
|
|
72
|
+
# openc3-cosmos-init/plugins/packages/openc3-vue-common/src/tools/admin/
|
|
73
|
+
# tabs/settings/<Name>Settings.vue, e.g. TimeZoneSettings.vue has
|
|
74
|
+
# `const settingName = 'time_zone'`. Then:
|
|
75
|
+
#
|
|
76
|
+
# type: what the reader SAVES and expects back, not what a control
|
|
77
|
+
# displays. Storing the wrong shape is silent - the reader either
|
|
78
|
+
# throws or ignores the value:
|
|
79
|
+
# :string plain text (a URL, a subtitle)
|
|
80
|
+
# :boolean real true/false; the frontend treats the string
|
|
81
|
+
# "false" as truthy, so these must not be text
|
|
82
|
+
# :json_text JSON kept as a String. A component that calls
|
|
83
|
+
# JSON.stringify before saveSetting and JSON.parse
|
|
84
|
+
# in parseSetting is this - handing it a parsed
|
|
85
|
+
# object makes its own JSON.parse throw
|
|
86
|
+
# :json JSON parsed into an object before storing, for a
|
|
87
|
+
# reader that checks the shape. AiChatConfig.load
|
|
88
|
+
# does `raw['data'].is_a?(Hash) ? ... : {}`, so text
|
|
89
|
+
# would be silently discarded
|
|
90
|
+
# values: the allowed values, or nil for free text.
|
|
91
|
+
# Copy them from the component's v-select items.
|
|
92
|
+
# require_keys: for :json/:json_text, top-level keys the blob must have.
|
|
93
|
+
# Use when a partial blob would break the reader rather than just
|
|
94
|
+
# fall back to a default. See 'system_health' below.
|
|
95
|
+
# example: for :json/:json_text, a valid value. There is no other way for
|
|
96
|
+
# an operator to discover the shape of a blob short of reading the
|
|
97
|
+
# component source, so every JSON setting needs one. Copy it from
|
|
98
|
+
# the JSON.stringify({...}) in the component's save method.
|
|
99
|
+
#
|
|
100
|
+
# Example, for a hypothetical LogLevelSettings.vue holding 'log_level':
|
|
101
|
+
#
|
|
102
|
+
# 'log_level' => { type: :string, values: ['DEBUG', 'INFO', 'WARN'] },
|
|
103
|
+
#
|
|
104
|
+
# No other change is needed - the env var (OPENC3_SETTING_LOG_LEVEL), the
|
|
105
|
+
# validation and the `cli initsettings --help` listing all follow.
|
|
106
|
+
KNOWN_SETTINGS = {
|
|
107
|
+
# Booleans. The frontend treats the string "false" as truthy, so these
|
|
108
|
+
# have to reach Redis as real booleans.
|
|
109
|
+
'ai_chat' => { type: :boolean, values: nil },
|
|
110
|
+
'news_feed' => { type: :boolean, values: nil },
|
|
111
|
+
'script_runner_locking' => { type: :boolean, values: nil },
|
|
112
|
+
'script_runner_lifecycle' => { type: :boolean, values: nil }, # Enterprise only
|
|
113
|
+
# Fixed choice strings
|
|
114
|
+
'time_zone' => { type: :string, values: ['local', 'UTC'] },
|
|
115
|
+
'time_format' => { type: :string, values: ['ampm', '24hr'] },
|
|
116
|
+
'theme' => { type: :string, values: ['cosmosDark', 'cosmosDarkCobalt', 'cosmosDarkIndigo',
|
|
117
|
+
'cosmosDarkSlate', 'cosmosDarkEmerald'] },
|
|
118
|
+
# Free text
|
|
119
|
+
'subtitle' => { type: :string, values: nil },
|
|
120
|
+
'source_url' => { type: :string, values: nil },
|
|
121
|
+
'rubygems_url' => { type: :string, values: nil },
|
|
122
|
+
'pypi_url' => { type: :string, values: nil },
|
|
123
|
+
# JSON *text*: these components JSON.stringify before saving and
|
|
124
|
+
# JSON.parse on load, so the stored value is a String, not an object
|
|
125
|
+
'astro' => { type: :json_text, values: nil,
|
|
126
|
+
example: '{"hideClock":false}' },
|
|
127
|
+
# require_keys because the Admin Console writes all five together and both
|
|
128
|
+
# readers assume that: a missing height reaches the stylesheet as
|
|
129
|
+
# "height: undefinedpx", which is invalid CSS, so the banner renders at
|
|
130
|
+
# auto height top AND bottom instead of staying hidden.
|
|
131
|
+
'classification_banner' => { type: :json_text, values: nil,
|
|
132
|
+
require_keys: ['text', 'fontColor', 'backgroundColor',
|
|
133
|
+
'topHeight', 'bottomHeight'],
|
|
134
|
+
example: '{"text":"UNCLASSIFIED","fontColor":"#ffffff",' \
|
|
135
|
+
'"backgroundColor":"#00cc00","topHeight":20,"bottomHeight":0}' },
|
|
136
|
+
'context_tag' => { type: :json_text, values: nil,
|
|
137
|
+
example: '{"text":"DEV","fontColor":"#ffffff","backgroundColor":"#ff0000"}' },
|
|
138
|
+
# Settings with no Admin Console tab of their own - see NO_ADMIN_TAB below
|
|
139
|
+
#
|
|
140
|
+
# Written as JSON text by ScopeModel#seed_database and re-read by the
|
|
141
|
+
# Enterprise metrics microservices. require_keys because a partial blob
|
|
142
|
+
# doesn't degrade gracefully: log_thresholds does
|
|
143
|
+
# data['global']['enableAlerts'] and passes data[metric_name] straight to
|
|
144
|
+
# check_persistent_threshold, so a blob missing either key raises rather
|
|
145
|
+
# than falling back, silently ending CPU/memory/disk alerting.
|
|
146
|
+
'system_health' => { type: :json_text, values: nil,
|
|
147
|
+
require_keys: ['cpu', 'memory', 'disk', 'global'],
|
|
148
|
+
example: '{"cpu":{"redThreshold":90.0,"yellowThreshold":80.0,"snoozeMinutes":15,' \
|
|
149
|
+
'"sustainedSeconds":15},"memory":{"redThreshold":90.0,' \
|
|
150
|
+
'"yellowThreshold":80.0,"snoozeMinutes":15,"sustainedSeconds":15},' \
|
|
151
|
+
'"disk":{"redThreshold":90.0,"yellowThreshold":80.0,' \
|
|
152
|
+
'"snoozeMinutes":720,"sustainedSeconds":60},' \
|
|
153
|
+
'"global":{"enableAlerts":true}}' },
|
|
154
|
+
# Enterprise AI chat provider/model config. :json, not :json_text -
|
|
155
|
+
# AiChatConfig.load ignores a String and falls back to {}
|
|
156
|
+
'ai_chat_config' => { type: :json, values: nil,
|
|
157
|
+
example: '{"provider":"anthropic","model":"claude-opus-4-7","base_url":""}' },
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
# Settings that KNOWN_SETTINGS lists on purpose despite having no
|
|
161
|
+
# *Settings.vue tab in this repo. The drift specs compare KNOWN_SETTINGS
|
|
162
|
+
# against those components, so without this list adding either of these
|
|
163
|
+
# would fail the "doesn't list a setting the Admin Console no longer has"
|
|
164
|
+
# check. Both are real settings that code reads.
|
|
165
|
+
NO_ADMIN_TAB = ['system_health', 'ai_chat_config']
|
|
166
|
+
|
|
24
167
|
# NOTE: The following three class methods are used by the ModelController
|
|
25
168
|
# and are reimplemented to enable various Model class methods to work
|
|
26
|
-
def self.get(name:, scope: nil)
|
|
169
|
+
def self.get(name:, scope: nil) # NOSONAR - scope: is part of the caller-facing signature
|
|
27
170
|
super(PRIMARY_KEY, name: name)
|
|
28
171
|
end
|
|
29
172
|
|
|
30
|
-
def self.names(scope: nil)
|
|
173
|
+
def self.names(scope: nil) # NOSONAR - scope: is part of the caller-facing signature
|
|
31
174
|
super(PRIMARY_KEY)
|
|
32
175
|
end
|
|
33
176
|
|
|
34
|
-
def self.all(scope: nil)
|
|
177
|
+
def self.all(scope: nil) # NOSONAR - scope: is part of the caller-facing signature
|
|
35
178
|
super(PRIMARY_KEY)
|
|
36
179
|
end
|
|
37
180
|
# END NOTE
|
|
38
181
|
|
|
182
|
+
# Seed settings from OPENC3_SETTING_<NAME> environment variables. Called by
|
|
183
|
+
# `openc3cli initsettings` during init container startup.
|
|
184
|
+
#
|
|
185
|
+
# By default a setting is only written when it does not already exist, so a
|
|
186
|
+
# value changed in the Admin Console survives a container restart. Set
|
|
187
|
+
# OPENC3_SETTINGS_OVERWRITE to write on every run instead.
|
|
188
|
+
#
|
|
189
|
+
# Nothing here aborts init by default. A bad setting name, a bad value, or a
|
|
190
|
+
# malformed control variable is reported on stdout and that one setting is
|
|
191
|
+
# skipped, leaving COSMOS on its built-in default. Set STRICT_ENV_VAR to
|
|
192
|
+
# fail init instead; --dry-run always fails.
|
|
193
|
+
#
|
|
194
|
+
# @param env [Hash] environment to read from, defaults to ENV
|
|
195
|
+
# @param overwrite [Boolean, nil] nil reads OVERWRITE_ENV_VAR from env
|
|
196
|
+
# @param strict [Boolean, nil] nil reads STRICT_ENV_VAR from env
|
|
197
|
+
# @param dry_run [Boolean] report what would happen and write nothing.
|
|
198
|
+
# Reports every problem rather than aborting on the first, and works
|
|
199
|
+
# before Redis is up so it can be run ahead of starting COSMOS.
|
|
200
|
+
# @return [Array<String>] names of the settings that were written
|
|
201
|
+
def self.apply_defaults(env: ENV, overwrite: nil, dry_run: false, strict: nil)
|
|
202
|
+
problems = []
|
|
203
|
+
strict = read_control_flag(env, STRICT_ENV_VAR, problems) if strict.nil?
|
|
204
|
+
overwrite = read_control_flag(env, OVERWRITE_ENV_VAR, problems) if overwrite.nil?
|
|
205
|
+
allow_unknown = read_control_flag(env, ALLOW_UNKNOWN_ENV_VAR, problems)
|
|
206
|
+
# Collects a value that can't be coerced, so OPENC3_SETTING_AI_CHAT=nope is
|
|
207
|
+
# reported like any other bad value rather than escaping as an exception
|
|
208
|
+
settings = parse_defaults_env(env, problems)
|
|
209
|
+
|
|
210
|
+
prefix = dry_run ? '[dry run] ' : ''
|
|
211
|
+
written = []
|
|
212
|
+
if settings.empty?
|
|
213
|
+
# Only when nothing matched the prefix. If every matching variable failed
|
|
214
|
+
# to coerce, settings is empty too, and claiming none were set is a lie -
|
|
215
|
+
# the errors reported below are the explanation
|
|
216
|
+
if problems.empty?
|
|
217
|
+
puts "#{prefix}No #{SETTING_ENV_PREFIX}* environment variables set - nothing to seed"
|
|
218
|
+
end
|
|
219
|
+
else
|
|
220
|
+
# A dry run is most useful before `openc3.sh start`, when there is no
|
|
221
|
+
# Redis to compare against. Names and values can still be checked.
|
|
222
|
+
comparable = dry_run ? redis_available? : true
|
|
223
|
+
puts "#{prefix}Redis is not reachable - checking names and values only" unless comparable
|
|
224
|
+
|
|
225
|
+
settings.each do |name, value|
|
|
226
|
+
begin
|
|
227
|
+
validate_setting!(name, value, allow_unknown: allow_unknown)
|
|
228
|
+
rescue StandardError => error
|
|
229
|
+
# Collect rather than abort. The init container restarts on failure
|
|
230
|
+
# (compose restart: on-failure, Kubernetes restartPolicy OnFailure),
|
|
231
|
+
# so raising here puts COSMOS in a crash loop over a cosmetic
|
|
232
|
+
# setting, with the cause buried in restarting container logs.
|
|
233
|
+
# Skipping leaves the setting at its default, which is the same
|
|
234
|
+
# outcome as not setting it, and the error is reported below.
|
|
235
|
+
problems << error.message
|
|
236
|
+
next
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
existing = comparable ? get(name: name) : nil
|
|
240
|
+
action, message = plan_setting(name, value, existing, overwrite)
|
|
241
|
+
message += ' (current value unknown)' unless comparable
|
|
242
|
+
puts "#{prefix}#{message}"
|
|
243
|
+
next if action == :skip
|
|
244
|
+
|
|
245
|
+
# :record leaves the setting alone and only refreshes provenance, so it
|
|
246
|
+
# is not reported as written
|
|
247
|
+
written << name if action == :write
|
|
248
|
+
next if dry_run
|
|
249
|
+
set({ name: name, data: value }, scope: nil) if action == :write
|
|
250
|
+
record_seeded(name, value)
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
report_problems(problems, prefix: prefix, dry_run: dry_run, strict: strict)
|
|
255
|
+
written
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
# Print every problem and decide whether it should end the process.
|
|
259
|
+
#
|
|
260
|
+
# A typo in a cosmetic setting must not put the init container in a restart
|
|
261
|
+
# loop with the cause buried in restarting container logs, so the default is
|
|
262
|
+
# report-and-continue. Two things opt into failing: --dry-run, which exists
|
|
263
|
+
# to be a preflight gate, and STRICT_ENV_VAR, for a deployment that would
|
|
264
|
+
# rather not come up at all than come up misconfigured.
|
|
265
|
+
def self.report_problems(problems, prefix:, dry_run:, strict:)
|
|
266
|
+
return if problems.empty?
|
|
267
|
+
problems.each { |problem| puts "#{prefix}ERROR: #{problem}" }
|
|
268
|
+
# "problem" rather than "setting" - a malformed control variable is
|
|
269
|
+
# reported here too, and it isn't a setting that got skipped
|
|
270
|
+
summary = "#{problems.length} #{SETTING_ENV_PREFIX}* configuration problem(s)"
|
|
271
|
+
if dry_run or strict
|
|
272
|
+
puts "#{prefix}#{summary}"
|
|
273
|
+
else
|
|
274
|
+
puts "#{prefix}#{summary} - the affected setting(s) were skipped and COSMOS will use the default"
|
|
275
|
+
puts "#{prefix}Set #{STRICT_ENV_VAR} to fail init on these instead of continuing"
|
|
276
|
+
end
|
|
277
|
+
$stdout.flush
|
|
278
|
+
raise "#{summary}: #{problems.join('; ')}" if dry_run or strict
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
# Read a boolean control variable, reporting an unparsable value rather than
|
|
282
|
+
# letting it abort init. Off is the safe reading of all three: OVERWRITE off
|
|
283
|
+
# doesn't discard an Admin Console edit, ALLOW_UNKNOWN off doesn't write a
|
|
284
|
+
# name nothing reads, and STRICT off doesn't fail init.
|
|
285
|
+
#
|
|
286
|
+
# @param problems [Array<String>] collects the message when the value is bad
|
|
287
|
+
# @return [Boolean]
|
|
288
|
+
def self.read_control_flag(env, name, problems)
|
|
289
|
+
truthy_env?(env, name)
|
|
290
|
+
rescue StandardError => error
|
|
291
|
+
problems << "#{error.message} - treating #{name} as off"
|
|
292
|
+
false
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
# What apply_defaults will do with one setting. Single-sourced so a dry run
|
|
296
|
+
# cannot report one thing and the real run do another.
|
|
297
|
+
#
|
|
298
|
+
# @return [Array(Symbol, String)] :write, :record or :skip, and the line to
|
|
299
|
+
# log. :record means the stored value is already correct but provenance
|
|
300
|
+
# still needs writing, so the setting itself is left untouched.
|
|
301
|
+
def self.plan_setting(name, value, existing, overwrite)
|
|
302
|
+
if existing.nil?
|
|
303
|
+
[:write, "Set default setting '#{name}' to: #{value.inspect}"]
|
|
304
|
+
elsif overwrite
|
|
305
|
+
if existing['data'] == value
|
|
306
|
+
# Rewriting the same value would bump updated_at on every init and
|
|
307
|
+
# report a write that changed nothing. Provenance is still recorded,
|
|
308
|
+
# so the next run without OVERWRITE can tell this value came from the
|
|
309
|
+
# environment rather than from an Admin Console edit.
|
|
310
|
+
[:record, "Setting '#{name}' already matches #{value.inspect}"]
|
|
311
|
+
else
|
|
312
|
+
# Overwrite discards an Admin Console edit, so say what was lost -
|
|
313
|
+
# otherwise the log reads identically to a first-time seed
|
|
314
|
+
[:write, "Overwriting setting '#{name}': #{existing['data'].inspect} -> " \
|
|
315
|
+
"#{value.inspect} (#{OVERWRITE_ENV_VAR} is set)"]
|
|
316
|
+
end
|
|
317
|
+
elsif !seeded_value?(name, existing['data'])
|
|
318
|
+
# Says what is known - that this value didn't come from here - rather
|
|
319
|
+
# than claiming it was edited. An unrecorded setting is the normal case
|
|
320
|
+
# on a deployment upgrading into this feature: nobody changed it, it was
|
|
321
|
+
# written by seed_database or by a release before provenance existed.
|
|
322
|
+
[:skip, "Setting '#{name}' holds a value initsettings didn't write - leaving as " \
|
|
323
|
+
"#{existing['data'].inspect} (set #{OVERWRITE_ENV_VAR} to replace it)"]
|
|
324
|
+
elsif existing['data'] == value
|
|
325
|
+
[:skip, "Setting '#{name}' already matches #{value.inspect} - leaving unchanged"]
|
|
326
|
+
else
|
|
327
|
+
[:write, "Updating unedited setting '#{name}': #{existing['data'].inspect} -> #{value.inspect}"]
|
|
328
|
+
end
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
def self.redis_available?
|
|
332
|
+
names()
|
|
333
|
+
true
|
|
334
|
+
rescue StandardError
|
|
335
|
+
false
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
# Provenance for the values this seeder wrote, so a later init can tell an
|
|
339
|
+
# untouched setting from one an operator changed in the Admin Console.
|
|
340
|
+
# Without it the seeder has two bad options: never update (so editing the
|
|
341
|
+
# env var does nothing) or always update (so Admin Console edits silently
|
|
342
|
+
# revert on every restart).
|
|
343
|
+
SEEDED_PRIMARY_KEY = 'openc3__settings_seeded'
|
|
344
|
+
|
|
345
|
+
# @return [Boolean] true when the current value is the one we last seeded,
|
|
346
|
+
# meaning nobody has changed it since.
|
|
347
|
+
#
|
|
348
|
+
# An unrecorded setting is NOT treated as seeded. It was set by the Admin
|
|
349
|
+
# Console, by seed_database, or by a release before this tracking existed,
|
|
350
|
+
# and overwriting it is the silent clobber this mechanism exists to
|
|
351
|
+
# prevent. The cost is that a deployment upgrading into this feature needs
|
|
352
|
+
# one run with OVERWRITE_ENV_VAR before env changes take effect again. That
|
|
353
|
+
# cost is documented for operators too - compose.override.yaml under
|
|
354
|
+
# OPENC3_SETTINGS_OVERWRITE and `cli initsettings --help` both say it -
|
|
355
|
+
# so keep those in sync with this behavior.
|
|
356
|
+
def self.seeded_value?(name, current)
|
|
357
|
+
recorded = Store.hget(SEEDED_PRIMARY_KEY, name)
|
|
358
|
+
return false if recorded.nil?
|
|
359
|
+
JSON.parse(recorded, allow_nan: true, create_additions: true)['data'] == current
|
|
360
|
+
rescue JSON::ParserError
|
|
361
|
+
false
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
def self.record_seeded(name, value)
|
|
365
|
+
Store.hset(SEEDED_PRIMARY_KEY, name, JSON.generate({ 'data' => value.as_json(allow_nan: true) }))
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
# Collect every OPENC3_SETTING_<NAME> variable into a name => value hash.
|
|
369
|
+
#
|
|
370
|
+
# @param env [Hash] environment to read from
|
|
371
|
+
# @param problems [Array<String>] collects a value that can't be coerced, so
|
|
372
|
+
# OPENC3_SETTING_AI_CHAT=nope is reported and skipped alongside every other
|
|
373
|
+
# bad value rather than escaping as an exception and aborting init
|
|
374
|
+
# @return [Hash] setting name => coerced value
|
|
375
|
+
def self.parse_defaults_env(env, problems = [])
|
|
376
|
+
settings = {}
|
|
377
|
+
env.each do |key, value|
|
|
378
|
+
key = to_str(key)
|
|
379
|
+
next unless key.start_with?(SETTING_ENV_PREFIX)
|
|
380
|
+
name = key[SETTING_ENV_PREFIX.length..-1].downcase
|
|
381
|
+
next if name.empty?
|
|
382
|
+
begin
|
|
383
|
+
settings[name] = coerce(name, to_str(value))
|
|
384
|
+
rescue StandardError => error
|
|
385
|
+
problems << error.message
|
|
386
|
+
end
|
|
387
|
+
end
|
|
388
|
+
settings
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
# Environment variables and local mode files are always strings, but a
|
|
392
|
+
# setting holds whatever the Admin Console stores - a boolean for 'ai_chat',
|
|
393
|
+
# a String for everything else, including the settings whose String happens
|
|
394
|
+
# to contain JSON.
|
|
395
|
+
#
|
|
396
|
+
# Driven by the declared type rather than by attempting JSON.parse on
|
|
397
|
+
# everything: parse-and-see turns 'classification_banner' into a Hash and
|
|
398
|
+
# the component's own JSON.parse then throws on it, and it would turn a
|
|
399
|
+
# subtitle of "2024" into a number.
|
|
400
|
+
#
|
|
401
|
+
# @param name [String] setting name
|
|
402
|
+
# @param value [String] raw value
|
|
403
|
+
# @return [Object] value in the form the frontend expects
|
|
404
|
+
def self.coerce(name, value)
|
|
405
|
+
return value unless value.is_a?(String)
|
|
406
|
+
case KNOWN_SETTINGS.dig(name, :type)
|
|
407
|
+
when :boolean
|
|
408
|
+
ConfigParser.handle_true_false_strict(value, description: "setting '#{name}'")
|
|
409
|
+
when :json
|
|
410
|
+
# Parsed, because this setting's reader checks for an object and
|
|
411
|
+
# discards text. Validated here so a malformed blob is reported at seed
|
|
412
|
+
# time rather than read back as a default nobody asked for.
|
|
413
|
+
parse_json!(name, value)
|
|
414
|
+
when :json_text
|
|
415
|
+
# Kept as text, but parsed anyway to prove it is valid - the component
|
|
416
|
+
# that JSON.parses it has no way to report a failure
|
|
417
|
+
parse_json!(name, value)
|
|
418
|
+
value
|
|
419
|
+
else
|
|
420
|
+
value
|
|
421
|
+
end
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
# @return [Object] the parsed blob
|
|
425
|
+
# @raise [RuntimeError] when the value isn't a JSON object with the keys the
|
|
426
|
+
# setting's reader requires
|
|
427
|
+
def self.parse_json!(name, value)
|
|
428
|
+
parsed = JSON.parse(value)
|
|
429
|
+
unless parsed.is_a?(Hash)
|
|
430
|
+
raise "Value for setting '#{name}' must be a JSON object, got #{parsed.class}"
|
|
431
|
+
end
|
|
432
|
+
required = KNOWN_SETTINGS.dig(name, :require_keys)
|
|
433
|
+
if required
|
|
434
|
+
missing = required - parsed.keys
|
|
435
|
+
unless missing.empty?
|
|
436
|
+
raise "Value for setting '#{name}' is missing required key(s): #{missing.join(', ')}. " \
|
|
437
|
+
"Seed the whole object - a partial one breaks the code that reads it"
|
|
438
|
+
end
|
|
439
|
+
end
|
|
440
|
+
parsed
|
|
441
|
+
rescue JSON::ParserError => error
|
|
442
|
+
raise "Value for setting '#{name}' is not valid JSON: #{error.message}"
|
|
443
|
+
end
|
|
444
|
+
|
|
445
|
+
# Largest value we will write. Settings are read into every browser tab, so
|
|
446
|
+
# this is a guard against an env var that is a file by mistake rather than a
|
|
447
|
+
# real limit - the biggest real setting is a few hundred bytes.
|
|
448
|
+
MAX_VALUE_BYTES = 64 * 1024
|
|
449
|
+
|
|
450
|
+
# Raise on anything we can prove is wrong, so the operator is told rather
|
|
451
|
+
# than left with a tool silently falling back to its built-in default.
|
|
452
|
+
#
|
|
453
|
+
# Raising here does NOT abort init: apply_defaults collects the message,
|
|
454
|
+
# skips that one setting and keeps going, because crash-looping the init
|
|
455
|
+
# container over a cosmetic setting would be worse than using the default.
|
|
456
|
+
# `initsettings --dry-run` is the mode that exits non-zero.
|
|
457
|
+
def self.validate_setting!(name, value, allow_unknown: false)
|
|
458
|
+
unless name =~ /\A[a-z0-9_]+\z/
|
|
459
|
+
raise "Invalid setting name #{name.inspect}. Names must be lowercase letters, numbers and underscores"
|
|
460
|
+
end
|
|
461
|
+
|
|
462
|
+
unless KNOWN_SETTINGS.key?(name)
|
|
463
|
+
unless allow_unknown
|
|
464
|
+
# Period here, not on the suggestion - without a near match the
|
|
465
|
+
# sentence used to run straight into "Set OPENC3_SETTINGS_ALLOW_UNKNOWN"
|
|
466
|
+
message = "'#{name}' is not a known COSMOS setting."
|
|
467
|
+
suggestion = KNOWN_SETTINGS.keys.find { |known| near_match?(known, name) }
|
|
468
|
+
message += " Did you mean '#{suggestion}'?" if suggestion
|
|
469
|
+
message += " Set #{ALLOW_UNKNOWN_ENV_VAR} to apply it anyway."
|
|
470
|
+
raise message
|
|
471
|
+
end
|
|
472
|
+
puts "WARNING: '#{name}' is not a known COSMOS setting - #{ALLOW_UNKNOWN_ENV_VAR} is set, applying anyway"
|
|
473
|
+
end
|
|
474
|
+
|
|
475
|
+
size = value.is_a?(String) ? value.bytesize : JSON.generate(value).bytesize
|
|
476
|
+
if size > MAX_VALUE_BYTES
|
|
477
|
+
raise "Value for setting '#{name}' is #{size} bytes, exceeds the #{MAX_VALUE_BYTES} byte limit"
|
|
478
|
+
end
|
|
479
|
+
|
|
480
|
+
allowed = KNOWN_SETTINGS.dig(name, :values)
|
|
481
|
+
return if allowed.nil? or allowed.include?(value)
|
|
482
|
+
# Report the coerced value so 'Local' vs 'local' is visible in the error
|
|
483
|
+
raise "Invalid value #{value.inspect} for setting '#{name}'. Must be one of: #{allowed.map(&:inspect).join(', ')}"
|
|
484
|
+
end
|
|
485
|
+
|
|
486
|
+
# The stored settings as paste-ready OPENC3_SETTING_* lines, for
|
|
487
|
+
# `cli initsettings --export`.
|
|
488
|
+
#
|
|
489
|
+
# This is the answer to "what do I put in a JSON setting" - configure it in
|
|
490
|
+
# the Admin Console, run --export, paste the line. Reading the shape out of
|
|
491
|
+
# a running system can't drift from the code the way a documented example
|
|
492
|
+
# can, and it captures the values an operator already tuned by hand.
|
|
493
|
+
#
|
|
494
|
+
# @param env_only [Boolean] skip settings that have no value stored
|
|
495
|
+
# @return [Array<String>] one compose "environment:" list item per setting
|
|
496
|
+
def self.export_lines
|
|
497
|
+
KNOWN_SETTINGS.keys.filter_map do |name|
|
|
498
|
+
setting = get(name: name)
|
|
499
|
+
next if setting.nil?
|
|
500
|
+
"- #{yaml_env_item(name, setting['data'])}"
|
|
501
|
+
end
|
|
502
|
+
end
|
|
503
|
+
|
|
504
|
+
# One "KEY=value" env entry, quoted when YAML would otherwise mangle it.
|
|
505
|
+
# JSON always contains '":', which YAML reads as a mapping, so those always
|
|
506
|
+
# need quoting - the exact trap the compose.override.yaml comments warn about.
|
|
507
|
+
def self.yaml_env_item(name, data)
|
|
508
|
+
value = data.is_a?(String) ? data : JSON.generate(data)
|
|
509
|
+
entry = "#{SETTING_ENV_PREFIX}#{name.upcase}=#{value}"
|
|
510
|
+
return entry unless entry =~ /[:#"'\\]|\A\s|\s\z/
|
|
511
|
+
%("#{entry.gsub('\\', '\\\\\\\\').gsub('"', '\\\\"')}")
|
|
512
|
+
end
|
|
513
|
+
|
|
514
|
+
# Every setting that can be seeded, with its allowed values, for the
|
|
515
|
+
# `cli initsettings --help` listing.
|
|
516
|
+
#
|
|
517
|
+
# @return [Array<String>] one 'name: allowed values' line per setting
|
|
518
|
+
def self.describe_settings
|
|
519
|
+
KNOWN_SETTINGS.map do |name, details|
|
|
520
|
+
allowed = if details[:values]
|
|
521
|
+
details[:values].join(', ')
|
|
522
|
+
else
|
|
523
|
+
case details[:type]
|
|
524
|
+
when :boolean then 'true, false (1, 0 also work)'
|
|
525
|
+
when :json, :json_text
|
|
526
|
+
keys = details[:require_keys]
|
|
527
|
+
keys ? "JSON object with keys: #{keys.join(', ')}" : 'JSON object'
|
|
528
|
+
else 'any text'
|
|
529
|
+
end
|
|
530
|
+
end
|
|
531
|
+
"#{name}: #{allowed}"
|
|
532
|
+
end
|
|
533
|
+
end
|
|
534
|
+
|
|
535
|
+
# The JSON settings with a valid example value, for `--help`. Kept separate
|
|
536
|
+
# from describe_settings because a blob is far too long for that one-line
|
|
537
|
+
# "name: allowed values" format.
|
|
538
|
+
#
|
|
539
|
+
# @return [Array<Array(String, String)>] name and example, in table order
|
|
540
|
+
def self.describe_json_settings
|
|
541
|
+
KNOWN_SETTINGS.filter_map do |name, details|
|
|
542
|
+
next unless [:json, :json_text].include?(details[:type])
|
|
543
|
+
[name, details[:example]]
|
|
544
|
+
end
|
|
545
|
+
end
|
|
546
|
+
|
|
547
|
+
# Cheap typo detection: same length with one character different, or one
|
|
548
|
+
# character inserted / deleted. Enough to catch 'time_zones' and 'timezone'
|
|
549
|
+
# without pulling in a Levenshtein dependency.
|
|
550
|
+
def self.near_match?(known, name)
|
|
551
|
+
return false if (known.length - name.length).abs > 1
|
|
552
|
+
long, short = known.length >= name.length ? [known, name] : [name, known]
|
|
553
|
+
i = 0
|
|
554
|
+
i += 1 while i < short.length and long[i] == short[i]
|
|
555
|
+
return true if i == short.length and long.length - short.length <= 1
|
|
556
|
+
if long.length == short.length
|
|
557
|
+
long[(i + 1)..-1] == short[(i + 1)..-1]
|
|
558
|
+
else
|
|
559
|
+
long[(i + 1)..-1] == short[i..-1]
|
|
560
|
+
end
|
|
561
|
+
end
|
|
562
|
+
|
|
563
|
+
# Read a boolean control variable. Unset means false.
|
|
564
|
+
#
|
|
565
|
+
# The OPENC3_NO_* install flags are enabled by presence, so 'VAR=0' turns
|
|
566
|
+
# them ON. That is fine for "skip installing a tool" and wrong here:
|
|
567
|
+
# OVERWRITE_ENV_VAR discards what an operator configured in the Admin
|
|
568
|
+
# Console, so '0' and 'false' have to mean off, as they read.
|
|
569
|
+
#
|
|
570
|
+
# @param env [Hash] environment to read from
|
|
571
|
+
# @param name [String] variable name
|
|
572
|
+
# @return [Boolean]
|
|
573
|
+
def self.truthy_env?(env, name)
|
|
574
|
+
ConfigParser.handle_true_false_strict(to_str(env[name]), description: name)
|
|
575
|
+
end
|
|
576
|
+
|
|
577
|
+
# ENV values are frozen Strings but a Hash passed in tests may hold symbols
|
|
578
|
+
def self.to_str(value)
|
|
579
|
+
value.nil? ? nil : value.to_s
|
|
580
|
+
end
|
|
581
|
+
|
|
39
582
|
def initialize(name:, scope: nil, data:)
|
|
40
583
|
super(PRIMARY_KEY, name: name, scope: scope)
|
|
41
584
|
@data = data
|
|
File without changes
|
|
File without changes
|
|
@@ -106,20 +106,17 @@ module OpenC3
|
|
|
106
106
|
targets = self.all(scope: scope)
|
|
107
107
|
targets.each { |_target_name, target| target['modified'] = false }
|
|
108
108
|
|
|
109
|
+
modified_targets = []
|
|
109
110
|
if ENV['OPENC3_LOCAL_MODE']
|
|
110
|
-
modified_targets
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
if targets.has_key?(target_name)
|
|
120
|
-
targets[target_name]['modified'] = true
|
|
121
|
-
end
|
|
122
|
-
end
|
|
111
|
+
modified_targets += OpenC3::LocalMode.modified_targets(scope: scope)
|
|
112
|
+
end
|
|
113
|
+
# Always list the bucket, even in local mode, since the local directory
|
|
114
|
+
# only holds what COSMOS itself wrote there
|
|
115
|
+
modified_targets += Bucket.getClient().list_files(bucket: ENV['OPENC3_CONFIG_BUCKET'], path: "#{scope}/targets_modified/", only_directories: true)
|
|
116
|
+
modified_targets.each do |target_name|
|
|
117
|
+
# A target could have been deleted without removing the modified files
|
|
118
|
+
# Thus we have to check for the existence of the target_name key
|
|
119
|
+
targets[target_name]['modified'] = true if targets[target_name]
|
|
123
120
|
end
|
|
124
121
|
# Sort (which turns hash to array) and return hash
|
|
125
122
|
# This enables a consistent listing of the targets
|
|
@@ -184,25 +181,33 @@ module OpenC3
|
|
|
184
181
|
end
|
|
185
182
|
|
|
186
183
|
# Given target's modified file list
|
|
184
|
+
# Returns the modified files as names relative to the scope,
|
|
185
|
+
# i.e. "INST/procedures/new.rb"
|
|
187
186
|
def self.modified_files(target_name, scope:)
|
|
188
187
|
modified = []
|
|
189
188
|
|
|
190
189
|
if ENV['OPENC3_LOCAL_MODE']
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
# The trailing slash is important!
|
|
196
|
-
prefix: "#{scope}/targets_modified/#{target_name}/",
|
|
197
|
-
)
|
|
198
|
-
resp.each do |item|
|
|
199
|
-
# Results look like DEFAULT/targets_modified/INST/procedures/new.rb
|
|
200
|
-
# so split on '/' and ignore the first two values
|
|
201
|
-
modified << item.key.split('/')[2..-1].join('/')
|
|
190
|
+
# LocalMode reports target relative paths, i.e. "procedures/new.rb",
|
|
191
|
+
# so add the target name to match the bucket listing below
|
|
192
|
+
modified += OpenC3::LocalMode.modified_files(target_name, scope: scope).map do |file_path|
|
|
193
|
+
"#{target_name}/#{file_path}"
|
|
202
194
|
end
|
|
203
195
|
end
|
|
196
|
+
# Always list the bucket, even in local mode. The local directory only
|
|
197
|
+
# holds what COSMOS itself wrote there, so a file placed directly in the
|
|
198
|
+
# bucket is modified but has no local copy.
|
|
199
|
+
resp = Bucket.getClient().list_objects(
|
|
200
|
+
bucket: ENV['OPENC3_CONFIG_BUCKET'],
|
|
201
|
+
# The trailing slash is important!
|
|
202
|
+
prefix: "#{scope}/targets_modified/#{target_name}/",
|
|
203
|
+
)
|
|
204
|
+
resp.each do |item|
|
|
205
|
+
# Results look like DEFAULT/targets_modified/INST/procedures/new.rb
|
|
206
|
+
# so split on '/' and ignore the first two values
|
|
207
|
+
modified << item.key.split('/')[2..-1].join('/')
|
|
208
|
+
end
|
|
204
209
|
# Sort to enable a consistent listing of the modified files
|
|
205
|
-
modified.sort
|
|
210
|
+
modified.uniq.sort
|
|
206
211
|
end
|
|
207
212
|
|
|
208
213
|
# files: optional list of specific modified files to delete, each a name
|
|
File without changes
|
|
File without changes
|
|
File without changes
|