openc3 5.2.0 → 5.3.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/data/config/interface_modifiers.yaml +22 -4
- data/data/config/microservice.yaml +18 -0
- data/lib/openc3/api/interface_api.rb +12 -0
- data/lib/openc3/api/router_api.rb +14 -2
- data/lib/openc3/api/target_api.rb +24 -3
- data/lib/openc3/api/tlm_api.rb +3 -3
- data/lib/openc3/interfaces/interface.rb +27 -26
- data/lib/openc3/interfaces/mqtt_interface.rb +240 -0
- data/lib/openc3/interfaces/protocols/override_protocol.rb +2 -61
- data/lib/openc3/interfaces/protocols/protocol.rb +6 -1
- data/lib/openc3/interfaces/simulated_target_interface.rb +1 -3
- data/lib/openc3/interfaces/tcpip_server_interface.rb +0 -11
- data/lib/openc3/interfaces.rb +2 -3
- data/lib/openc3/logs/buffered_packet_log_reader.rb +2 -2
- data/lib/openc3/microservices/interface_microservice.rb +47 -3
- data/lib/openc3/microservices/microservice.rb +6 -0
- data/lib/openc3/models/cvt_model.rb +103 -47
- data/lib/openc3/models/interface_model.rb +23 -0
- data/lib/openc3/models/microservice_model.rb +7 -0
- data/lib/openc3/models/model.rb +1 -1
- data/lib/openc3/models/secret_model.rb +53 -0
- data/lib/openc3/operators/microservice_operator.rb +25 -0
- data/lib/openc3/script/api_shared.rb +18 -2
- data/lib/openc3/topics/interface_topic.rb +16 -0
- data/lib/openc3/topics/router_topic.rb +16 -0
- data/lib/openc3/utilities/redis_secrets.rb +46 -0
- data/lib/openc3/utilities/secrets.rb +63 -0
- data/lib/openc3/version.rb +5 -5
- data/templates/plugin-template/README.md +4 -3
- metadata +20 -2
|
@@ -58,8 +58,6 @@ module OpenC3
|
|
|
58
58
|
attr_accessor :raw_logger_pair
|
|
59
59
|
# @return [String] The ip address to bind to. Default to ANY (0.0.0.0)
|
|
60
60
|
attr_accessor :listen_address
|
|
61
|
-
# @return [boolean] Automatically send SYSTEM META on connect - Default false - Can be CMD/TLM
|
|
62
|
-
attr_accessor :auto_system_meta
|
|
63
61
|
|
|
64
62
|
# @param write_port [Integer] The server write port. Clients should connect
|
|
65
63
|
# and expect to receive data from this port.
|
|
@@ -114,7 +112,6 @@ module OpenC3
|
|
|
114
112
|
@raw_logging_enabled = false
|
|
115
113
|
@connection_mutex = Mutex.new
|
|
116
114
|
@listen_address = "0.0.0.0"
|
|
117
|
-
@auto_system_meta = false
|
|
118
115
|
|
|
119
116
|
@read_allowed = false unless ConfigParser.handle_nil(read_port)
|
|
120
117
|
@write_allowed = false unless ConfigParser.handle_nil(write_port)
|
|
@@ -282,15 +279,12 @@ module OpenC3
|
|
|
282
279
|
|
|
283
280
|
# Supported Options
|
|
284
281
|
# LISTEN_ADDRESS - Ip address of the interface to accept connections on - Default: 0.0.0.0
|
|
285
|
-
# AUTO_SYSTEM_META - Automatically send SYSTEM META on connect - Default false
|
|
286
282
|
# (see Interface#set_option)
|
|
287
283
|
def set_option(option_name, option_values)
|
|
288
284
|
super(option_name, option_values)
|
|
289
285
|
case option_name.upcase
|
|
290
286
|
when 'LISTEN_ADDRESS'
|
|
291
287
|
@listen_address = option_values[0]
|
|
292
|
-
when 'AUTO_SYSTEM_META'
|
|
293
|
-
@auto_system_meta = ConfigParser.handle_true_false(option_values[0])
|
|
294
288
|
end
|
|
295
289
|
end
|
|
296
290
|
|
|
@@ -411,11 +405,6 @@ module OpenC3
|
|
|
411
405
|
interface.connect
|
|
412
406
|
|
|
413
407
|
if listen_write
|
|
414
|
-
if @auto_system_meta
|
|
415
|
-
meta_packet = System.telemetry.packet('SYSTEM', 'META').clone
|
|
416
|
-
interface.write(meta_packet)
|
|
417
|
-
end
|
|
418
|
-
|
|
419
408
|
@write_connection_callback.call(interface) if @write_connection_callback
|
|
420
409
|
@connection_mutex.synchronize do
|
|
421
410
|
@write_interface_infos << InterfaceInfo.new(interface, hostname, host_ip, port)
|
data/lib/openc3/interfaces.rb
CHANGED
|
@@ -17,11 +17,12 @@
|
|
|
17
17
|
# All changes Copyright 2022, OpenC3, Inc.
|
|
18
18
|
# All Rights Reserved
|
|
19
19
|
#
|
|
20
|
-
# This file may also be used under the terms of a commercial license
|
|
20
|
+
# This file may also be used under the terms of a commercial license
|
|
21
21
|
# if purchased from OpenC3, Inc.
|
|
22
22
|
|
|
23
23
|
module OpenC3
|
|
24
24
|
autoload(:Interface, 'openc3/interfaces/interface.rb')
|
|
25
|
+
autoload(:MqttInterface, 'openc3/interfaces/mqtt_interface.rb')
|
|
25
26
|
autoload(:StreamInterface, 'openc3/interfaces/stream_interface.rb')
|
|
26
27
|
autoload(:SerialInterface, 'openc3/interfaces/serial_interface.rb')
|
|
27
28
|
autoload(:SimulatedTargetInterface, 'openc3/interfaces/simulated_target_interface.rb')
|
|
@@ -31,7 +32,6 @@ module OpenC3
|
|
|
31
32
|
autoload(:LincInterface, 'openc3/interfaces/linc_interface.rb')
|
|
32
33
|
autoload(:LincHandshakeCommand, 'openc3/interfaces/linc_interface.rb')
|
|
33
34
|
autoload(:LincHandshake, 'openc3/interfaces/linc_interface.rb')
|
|
34
|
-
autoload(:DartStatusInterface, 'openc3/interfaces/dart_status_interface.rb')
|
|
35
35
|
|
|
36
36
|
autoload(:Protocol, 'openc3/interfaces/protocols/protocol.rb')
|
|
37
37
|
autoload(:BurstProtocol, 'openc3/interfaces/protocols/burst_protocol.rb')
|
|
@@ -41,7 +41,6 @@ module OpenC3
|
|
|
41
41
|
autoload(:TemplateProtocol, 'openc3/interfaces/protocols/template_protocol.rb')
|
|
42
42
|
autoload(:TerminatedProtocol, 'openc3/interfaces/protocols/terminated_protocol.rb')
|
|
43
43
|
|
|
44
|
-
autoload(:OverrideProtocol, 'openc3/interfaces/protocols/override_protocol.rb')
|
|
45
44
|
autoload(:CrcProtocol, 'openc3/interfaces/protocols/crc_protocol.rb')
|
|
46
45
|
autoload(:IgnorePacketProtocol, 'openc3/interfaces/protocols/ignore_packet_protocol.rb')
|
|
47
46
|
end
|
|
@@ -31,8 +31,8 @@ module OpenC3
|
|
|
31
31
|
@buffer_depth = buffer_depth
|
|
32
32
|
end
|
|
33
33
|
|
|
34
|
-
def next_packet_time
|
|
35
|
-
fill_buffer()
|
|
34
|
+
def next_packet_time(identify_and_define = true)
|
|
35
|
+
fill_buffer(identify_and_define)
|
|
36
36
|
packet = @buffer[0]
|
|
37
37
|
return packet.packet_time if packet
|
|
38
38
|
return nil
|
|
@@ -107,6 +107,28 @@ module OpenC3
|
|
|
107
107
|
end
|
|
108
108
|
next 'SUCCESS'
|
|
109
109
|
end
|
|
110
|
+
if msg_hash.key?('interface_cmd')
|
|
111
|
+
params = JSON.parse(msg_hash['interface_cmd'], allow_nan: true, create_additions: true)
|
|
112
|
+
begin
|
|
113
|
+
@logger.info "#{@interface.name}: interface_cmd: #{params['cmd_name']} #{params['cmd_params'].join(' ')}"
|
|
114
|
+
@interface.interface_cmd(params['cmd_name'], params['cmd_params'])
|
|
115
|
+
rescue => e
|
|
116
|
+
@logger.error "#{@interface.name}: interface_cmd: #{e.formatted}"
|
|
117
|
+
next e.message
|
|
118
|
+
end
|
|
119
|
+
next 'SUCCESS'
|
|
120
|
+
end
|
|
121
|
+
if msg_hash.key?('protocol_cmd')
|
|
122
|
+
params = JSON.parse(msg_hash['protocol_cmd'], allow_nan: true, create_additions: true)
|
|
123
|
+
begin
|
|
124
|
+
@logger.info "#{@interface.name}: protocol_cmd: #{params['cmd_name']} #{params['cmd_params'].join(' ')} read_write: #{params['read_write']} index: #{params['index']}"
|
|
125
|
+
@interface.protocol_cmd(params['cmd_name'], params['cmd_params'], read_write: params['read_write'], index: params['index'])
|
|
126
|
+
rescue => e
|
|
127
|
+
@logger.error "#{@interface.name}: protocol_cmd: #{e.formatted}"
|
|
128
|
+
next e.message
|
|
129
|
+
end
|
|
130
|
+
next 'SUCCESS'
|
|
131
|
+
end
|
|
110
132
|
end
|
|
111
133
|
|
|
112
134
|
target_name = msg_hash['target_name']
|
|
@@ -234,6 +256,28 @@ module OpenC3
|
|
|
234
256
|
@router.stop_raw_logging
|
|
235
257
|
end
|
|
236
258
|
end
|
|
259
|
+
if msg_hash.key?('router_cmd')
|
|
260
|
+
params = JSON.parse(msg_hash['router_cmd'], allow_nan: true, create_additions: true)
|
|
261
|
+
begin
|
|
262
|
+
@logger.info "#{@router.name}: router_cmd: #{params['cmd_name']} #{params['cmd_params'].join(' ')}"
|
|
263
|
+
@router.interface_cmd(params['cmd_name'], params['cmd_params'])
|
|
264
|
+
rescue => e
|
|
265
|
+
@logger.error "#{@router.name}: router_cmd: #{e.formatted}"
|
|
266
|
+
next e.message
|
|
267
|
+
end
|
|
268
|
+
next 'SUCCESS'
|
|
269
|
+
end
|
|
270
|
+
if msg_hash.key?('protocol_cmd')
|
|
271
|
+
params = JSON.parse(msg_hash['protocol_cmd'], allow_nan: true, create_additions: true)
|
|
272
|
+
begin
|
|
273
|
+
@logger.info "#{@router.name}: protocol_cmd: #{params['cmd_name']} #{params['cmd_params'].join(' ')} read_write: #{params['read_write']} index: #{params['index']}"
|
|
274
|
+
@router.protocol_cmd(params['cmd_name'], params['cmd_params'], read_write: params['read_write'], index: params['index'])
|
|
275
|
+
rescue => e
|
|
276
|
+
@logger.error "#{@router.name}: protoco_cmd: #{e.formatted}"
|
|
277
|
+
next e.message
|
|
278
|
+
end
|
|
279
|
+
next 'SUCCESS'
|
|
280
|
+
end
|
|
237
281
|
next 'SUCCESS'
|
|
238
282
|
end
|
|
239
283
|
|
|
@@ -264,6 +308,7 @@ module OpenC3
|
|
|
264
308
|
UNKNOWN_BYTES_TO_PRINT = 16
|
|
265
309
|
|
|
266
310
|
def initialize(name)
|
|
311
|
+
@mutex = Mutex.new
|
|
267
312
|
super(name)
|
|
268
313
|
@interface_or_router = self.class.name.to_s.split("Microservice")[0].upcase.split("::")[-1]
|
|
269
314
|
@scope = name.split("__")[0]
|
|
@@ -307,7 +352,6 @@ module OpenC3
|
|
|
307
352
|
@cancel_thread = false
|
|
308
353
|
@connection_failed_messages = []
|
|
309
354
|
@connection_lost_messages = []
|
|
310
|
-
@mutex = Mutex.new
|
|
311
355
|
if @interface_or_router == 'INTERFACE'
|
|
312
356
|
@handler_thread = InterfaceCmdHandlerThread.new(@interface, self, logger: @logger, scope: @scope)
|
|
313
357
|
else
|
|
@@ -560,7 +604,7 @@ module OpenC3
|
|
|
560
604
|
|
|
561
605
|
# Disconnect from the interface and stop the thread
|
|
562
606
|
def stop
|
|
563
|
-
@logger.info "#{@interface.name}: stop requested"
|
|
607
|
+
@logger.info "#{@interface ? @interface.name : @name}: stop requested"
|
|
564
608
|
@mutex.synchronize do
|
|
565
609
|
# Need to make sure that @cancel_thread is set and the interface disconnected within
|
|
566
610
|
# mutex to ensure that connect() is not called when we want to stop()
|
|
@@ -578,7 +622,7 @@ module OpenC3
|
|
|
578
622
|
end
|
|
579
623
|
|
|
580
624
|
def shutdown(sig = nil)
|
|
581
|
-
@logger.info "#{@interface.name}: shutdown requested"
|
|
625
|
+
@logger.info "#{@interface ? @interface.name : @name}: shutdown requested"
|
|
582
626
|
stop()
|
|
583
627
|
super()
|
|
584
628
|
end
|
|
@@ -27,6 +27,7 @@ OpenC3.require_file 'fileutils'
|
|
|
27
27
|
OpenC3.require_file 'openc3/utilities/zip'
|
|
28
28
|
OpenC3.require_file 'openc3/utilities/store'
|
|
29
29
|
OpenC3.require_file 'openc3/utilities/bucket'
|
|
30
|
+
OpenC3.require_file 'openc3/utilities/secrets'
|
|
30
31
|
OpenC3.require_file 'openc3/utilities/sleeper'
|
|
31
32
|
OpenC3.require_file 'openc3/utilities/open_telemetry'
|
|
32
33
|
OpenC3.require_file 'openc3/models/microservice_model'
|
|
@@ -43,6 +44,7 @@ module OpenC3
|
|
|
43
44
|
attr_accessor :custom
|
|
44
45
|
attr_accessor :scope
|
|
45
46
|
attr_accessor :logger
|
|
47
|
+
attr_accessor :secrets
|
|
46
48
|
|
|
47
49
|
def self.run(name = nil)
|
|
48
50
|
name = ENV['OPENC3_MICROSERVICE_NAME'] unless name
|
|
@@ -93,6 +95,7 @@ module OpenC3
|
|
|
93
95
|
@logger = Logger.new
|
|
94
96
|
@logger.scope = @scope
|
|
95
97
|
@logger.microservice_name = @name
|
|
98
|
+
@secrets = Secrets.getClient
|
|
96
99
|
|
|
97
100
|
OpenC3.setup_open_telemetry(@name, false)
|
|
98
101
|
|
|
@@ -104,6 +107,9 @@ module OpenC3
|
|
|
104
107
|
if @config
|
|
105
108
|
@topics = @config['topics']
|
|
106
109
|
@plugin = @config['plugin']
|
|
110
|
+
if @config['secrets']
|
|
111
|
+
@secrets.setup(@config['secrets'])
|
|
112
|
+
end
|
|
107
113
|
else
|
|
108
114
|
@config = {}
|
|
109
115
|
@plugin = nil
|
|
@@ -25,30 +25,29 @@ require 'openc3/utilities/store'
|
|
|
25
25
|
module OpenC3
|
|
26
26
|
class CvtModel
|
|
27
27
|
VALUE_TYPES = [:RAW, :CONVERTED, :FORMATTED, :WITH_UNITS]
|
|
28
|
-
# Stores telemetry item overrides which are returned on every request to get_item
|
|
29
|
-
@overrides = {}
|
|
30
|
-
|
|
31
28
|
def self.build_json_from_packet(packet)
|
|
32
29
|
packet.decom
|
|
33
30
|
end
|
|
34
31
|
|
|
35
32
|
# Delete the current value table for a target
|
|
36
|
-
def self.del(target_name:, packet_name:, scope:)
|
|
33
|
+
def self.del(target_name:, packet_name:, scope: $openc3_scope)
|
|
37
34
|
Store.hdel("#{scope}__tlm__#{target_name}", packet_name)
|
|
38
35
|
end
|
|
39
36
|
|
|
40
37
|
# Set the current value table for a target, packet
|
|
41
|
-
def self.set(hash, target_name:, packet_name:, scope:)
|
|
38
|
+
def self.set(hash, target_name:, packet_name:, scope: $openc3_scope)
|
|
42
39
|
Store.hset("#{scope}__tlm__#{target_name}", packet_name, JSON.generate(hash.as_json(:allow_nan => true)))
|
|
43
40
|
end
|
|
44
41
|
|
|
45
42
|
# Set an item in the current value table
|
|
46
|
-
def self.set_item(target_name, packet_name, item_name, value, type:, scope:)
|
|
43
|
+
def self.set_item(target_name, packet_name, item_name, value, type:, scope: $openc3_scope)
|
|
47
44
|
case type
|
|
48
45
|
when :WITH_UNITS
|
|
49
46
|
field = "#{item_name}__U"
|
|
47
|
+
value = value.to_s # WITH_UNITS should always be a string
|
|
50
48
|
when :FORMATTED
|
|
51
49
|
field = "#{item_name}__F"
|
|
50
|
+
value = value.to_s # FORMATTED should always be a string
|
|
52
51
|
when :CONVERTED
|
|
53
52
|
field = "#{item_name}__C"
|
|
54
53
|
when :RAW
|
|
@@ -62,27 +61,37 @@ module OpenC3
|
|
|
62
61
|
end
|
|
63
62
|
|
|
64
63
|
# Get an item from the current value table
|
|
65
|
-
def self.get_item(target_name, packet_name, item_name, type:, scope:)
|
|
66
|
-
|
|
67
|
-
return @overrides["#{target_name}__#{packet_name}__#{item_name}__#{type}"]
|
|
68
|
-
end
|
|
69
|
-
|
|
64
|
+
def self.get_item(target_name, packet_name, item_name, type:, scope: $openc3_scope)
|
|
65
|
+
override_key = item_name
|
|
70
66
|
types = []
|
|
71
67
|
case type
|
|
72
68
|
when :WITH_UNITS
|
|
73
69
|
types = ["#{item_name}__U", "#{item_name}__F", "#{item_name}__C", item_name]
|
|
70
|
+
override_key = "#{item_name}__U"
|
|
74
71
|
when :FORMATTED
|
|
75
72
|
types = ["#{item_name}__F", "#{item_name}__C", item_name]
|
|
73
|
+
override_key = "#{item_name}__F"
|
|
76
74
|
when :CONVERTED
|
|
77
75
|
types = ["#{item_name}__C", item_name]
|
|
76
|
+
override_key = "#{item_name}__C"
|
|
78
77
|
when :RAW
|
|
79
78
|
types = [item_name]
|
|
80
79
|
else
|
|
81
80
|
raise "Unknown type '#{type}' for #{target_name} #{packet_name} #{item_name}"
|
|
82
81
|
end
|
|
82
|
+
overrides = Store.hget("#{scope}__override__#{target_name}", packet_name)
|
|
83
|
+
if overrides
|
|
84
|
+
result = JSON.parse(overrides, :allow_nan => true, :create_additions => true)[override_key]
|
|
85
|
+
return result if result
|
|
86
|
+
end
|
|
83
87
|
hash = JSON.parse(Store.hget("#{scope}__tlm__#{target_name}", packet_name), :allow_nan => true, :create_additions => true)
|
|
84
88
|
hash.values_at(*types).each do |result|
|
|
85
|
-
|
|
89
|
+
if result
|
|
90
|
+
if type == :FORMATTED or type == :WITH_UNITS
|
|
91
|
+
return result.to_s
|
|
92
|
+
end
|
|
93
|
+
return result
|
|
94
|
+
end
|
|
86
95
|
end
|
|
87
96
|
return nil
|
|
88
97
|
end
|
|
@@ -97,10 +106,11 @@ module OpenC3
|
|
|
97
106
|
results = []
|
|
98
107
|
lookups = []
|
|
99
108
|
packet_lookup = {}
|
|
109
|
+
overrides = {}
|
|
100
110
|
# First generate a lookup hash of all the items represented so we can query the CVT
|
|
101
|
-
items.each { |item| _parse_item(lookups, item) }
|
|
111
|
+
items.each { |item| _parse_item(lookups, overrides, item, scope: scope) }
|
|
102
112
|
|
|
103
|
-
lookups.each do |target_packet_key, target_name, packet_name,
|
|
113
|
+
lookups.each do |target_packet_key, target_name, packet_name, value_keys|
|
|
104
114
|
unless packet_lookup[target_packet_key]
|
|
105
115
|
packet = Store.hget("#{scope}__tlm__#{target_name}", packet_name)
|
|
106
116
|
raise "Packet '#{target_name} #{packet_name}' does not exist" unless packet
|
|
@@ -108,23 +118,26 @@ module OpenC3
|
|
|
108
118
|
end
|
|
109
119
|
hash = packet_lookup[target_packet_key]
|
|
110
120
|
item_result = []
|
|
111
|
-
|
|
112
|
-
item_result[0] =
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
121
|
+
if value_keys.is_a?(Hash) # Set in _parse_item to indicate override
|
|
122
|
+
item_result[0] = value_keys['value']
|
|
123
|
+
else
|
|
124
|
+
value_keys.each do |key|
|
|
125
|
+
item_result[0] = hash[key]
|
|
126
|
+
break if item_result[0] # We want the first value
|
|
127
|
+
end
|
|
128
|
+
# If we were able to find a value, try to get the limits state
|
|
129
|
+
if item_result[0]
|
|
130
|
+
if now - hash['RECEIVED_TIMESECONDS'] > stale_time
|
|
131
|
+
item_result[1] = :STALE
|
|
132
|
+
else
|
|
133
|
+
# The last key is simply the name (RAW) so we can append __L
|
|
134
|
+
# If there is no limits then it returns nil which is acceptable
|
|
135
|
+
item_result[1] = hash["#{value_keys[-1]}__L"]
|
|
136
|
+
item_result[1] = item_result[1].intern if item_result[1] # Convert to symbol
|
|
137
|
+
end
|
|
119
138
|
else
|
|
120
|
-
#
|
|
121
|
-
# If there is no limits then it returns nil which is acceptable
|
|
122
|
-
item_result[1] = hash["#{packet_values[-1]}__L"]
|
|
123
|
-
item_result[1] = item_result[1].intern if item_result[1] # Convert to symbol
|
|
139
|
+
raise "Item '#{target_name} #{packet_name} #{value_keys[-1]}' does not exist" unless hash.key?(value_keys[-1])
|
|
124
140
|
end
|
|
125
|
-
else
|
|
126
|
-
raise "Item '#{target_name} #{packet_name} #{packet_values[-1]}' does not exist" unless hash.key?(packet_values[-1])
|
|
127
|
-
item_result[1] = nil
|
|
128
141
|
end
|
|
129
142
|
results << item_result
|
|
130
143
|
end
|
|
@@ -133,35 +146,64 @@ module OpenC3
|
|
|
133
146
|
|
|
134
147
|
# Override a current value table item such that it always returns the same value
|
|
135
148
|
# for the given type
|
|
136
|
-
def self.override(target_name, packet_name, item_name, value, type
|
|
137
|
-
|
|
138
|
-
|
|
149
|
+
def self.override(target_name, packet_name, item_name, value, type: :ALL, scope: $openc3_scope)
|
|
150
|
+
hash = Store.hget("#{scope}__override__#{target_name}", packet_name)
|
|
151
|
+
hash = JSON.parse(hash, :allow_nan => true, :create_additions => true) if hash
|
|
152
|
+
hash ||= {} # In case the above didn't create anything
|
|
153
|
+
case type
|
|
154
|
+
when :ALL
|
|
155
|
+
hash[item_name] = value
|
|
156
|
+
hash["#{item_name}__C"] = value
|
|
157
|
+
hash["#{item_name}__F"] = value.to_s
|
|
158
|
+
hash["#{item_name}__U"] = value.to_s
|
|
159
|
+
when :RAW
|
|
160
|
+
hash[item_name] = value
|
|
161
|
+
when :CONVERTED
|
|
162
|
+
hash["#{item_name}__C"] = value
|
|
163
|
+
when :FORMATTED
|
|
164
|
+
hash["#{item_name}__F"] = value.to_s # Always a String
|
|
165
|
+
when :WITH_UNITS
|
|
166
|
+
hash["#{item_name}__U"] = value.to_s # Always a String
|
|
139
167
|
else
|
|
140
168
|
raise "Unknown type '#{type}' for #{target_name} #{packet_name} #{item_name}"
|
|
141
169
|
end
|
|
170
|
+
Store.hset("#{scope}__override__#{target_name}", packet_name, JSON.generate(hash.as_json(:allow_nan => true)))
|
|
142
171
|
end
|
|
143
172
|
|
|
144
173
|
# Normalize a current value table item such that it returns the actual value
|
|
145
174
|
def self.normalize(target_name, packet_name, item_name, type: :ALL, scope: $openc3_scope)
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
175
|
+
hash = Store.hget("#{scope}__override__#{target_name}", packet_name)
|
|
176
|
+
hash = JSON.parse(hash, :allow_nan => true, :create_additions => true) if hash
|
|
177
|
+
hash ||= {} # In case the above didn't create anything
|
|
178
|
+
case type
|
|
179
|
+
when :ALL
|
|
180
|
+
hash.delete(item_name)
|
|
181
|
+
hash.delete("#{item_name}__C")
|
|
182
|
+
hash.delete("#{item_name}__F")
|
|
183
|
+
hash.delete("#{item_name}__U")
|
|
184
|
+
when :RAW
|
|
185
|
+
hash.delete(item_name)
|
|
186
|
+
when :CONVERTED
|
|
187
|
+
hash.delete("#{item_name}__C")
|
|
188
|
+
when :FORMATTED
|
|
189
|
+
hash.delete("#{item_name}__F")
|
|
190
|
+
when :WITH_UNITS
|
|
191
|
+
hash.delete("#{item_name}__U")
|
|
150
192
|
else
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
193
|
+
raise "Unknown type '#{type}' for #{target_name} #{packet_name} #{item_name}"
|
|
194
|
+
end
|
|
195
|
+
if hash.empty?
|
|
196
|
+
Store.hdel("#{scope}__override__#{target_name}", packet_name)
|
|
197
|
+
else
|
|
198
|
+
Store.hset("#{scope}__override__#{target_name}", packet_name, JSON.generate(hash.as_json(:allow_nan => true)))
|
|
156
199
|
end
|
|
157
200
|
end
|
|
158
201
|
|
|
159
202
|
# PRIVATE METHODS
|
|
160
203
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
# return an ordered array of hash with keys
|
|
204
|
+
# parse item and update lookups with packet_name and target_name and keys
|
|
205
|
+
# return an ordered array of hash with keys
|
|
206
|
+
def self._parse_item(lookups, overrides, item, scope:)
|
|
165
207
|
target_name, packet_name, item_name, value_type = item.split('__')
|
|
166
208
|
raise ArgumentError, "items must be formatted as TGT__PKT__ITEM__TYPE" if target_name.nil? || packet_name.nil? || item_name.nil? || value_type.nil?
|
|
167
209
|
|
|
@@ -177,9 +219,23 @@ module OpenC3
|
|
|
177
219
|
when 'WITH_UNITS'
|
|
178
220
|
keys = ["#{item_name}__U", "#{item_name}__F", "#{item_name}__C", item_name]
|
|
179
221
|
else
|
|
180
|
-
raise "Unknown value type #{value_type}"
|
|
222
|
+
raise "Unknown value type '#{value_type}'"
|
|
223
|
+
end
|
|
224
|
+
tgt_pkt_key = "#{target_name}__#{packet_name}"
|
|
225
|
+
# Check the overrides cache for this target / packet
|
|
226
|
+
unless overrides[tgt_pkt_key]
|
|
227
|
+
override_data = Store.hget("#{scope}__override__#{target_name}", packet_name)
|
|
228
|
+
if override_data
|
|
229
|
+
overrides[tgt_pkt_key] = JSON.parse(override_data, :allow_nan => true, :create_additions => true)
|
|
230
|
+
else
|
|
231
|
+
overrides[tgt_pkt_key] = {}
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
if overrides[tgt_pkt_key][keys[0]]
|
|
235
|
+
# Set the result as a Hash to distingish it from the key array and from an overridden Array value
|
|
236
|
+
keys = {'value' => overrides[tgt_pkt_key][keys[0]]}
|
|
181
237
|
end
|
|
182
|
-
lookups << [
|
|
238
|
+
lookups << [tgt_pkt_key, target_name, packet_name, keys]
|
|
183
239
|
end
|
|
184
240
|
end
|
|
185
241
|
end
|
|
@@ -38,11 +38,13 @@ module OpenC3
|
|
|
38
38
|
attr_accessor :reconnect_delay
|
|
39
39
|
attr_accessor :disable_disconnect
|
|
40
40
|
attr_accessor :options
|
|
41
|
+
attr_accessor :secret_options
|
|
41
42
|
attr_accessor :protocols
|
|
42
43
|
attr_accessor :interfaces
|
|
43
44
|
attr_accessor :log
|
|
44
45
|
attr_accessor :log_raw
|
|
45
46
|
attr_accessor :needs_dependencies
|
|
47
|
+
attr_accessor :secrets
|
|
46
48
|
|
|
47
49
|
# NOTE: The following three class methods are used by the ModelController
|
|
48
50
|
# and are reimplemented to enable various Model class methods to work
|
|
@@ -101,12 +103,14 @@ module OpenC3
|
|
|
101
103
|
reconnect_delay: 5.0,
|
|
102
104
|
disable_disconnect: false,
|
|
103
105
|
options: [],
|
|
106
|
+
secret_options: [],
|
|
104
107
|
protocols: [],
|
|
105
108
|
log: true,
|
|
106
109
|
log_raw: false,
|
|
107
110
|
updated_at: nil,
|
|
108
111
|
plugin: nil,
|
|
109
112
|
needs_dependencies: false,
|
|
113
|
+
secrets: [],
|
|
110
114
|
scope:
|
|
111
115
|
)
|
|
112
116
|
if self.class._get_type == 'INTERFACE'
|
|
@@ -123,10 +127,12 @@ module OpenC3
|
|
|
123
127
|
@reconnect_delay = reconnect_delay
|
|
124
128
|
@disable_disconnect = disable_disconnect
|
|
125
129
|
@options = options
|
|
130
|
+
@secret_options = secret_options
|
|
126
131
|
@protocols = protocols
|
|
127
132
|
@log = log
|
|
128
133
|
@log_raw = log_raw
|
|
129
134
|
@needs_dependencies = needs_dependencies
|
|
135
|
+
@secrets = secrets
|
|
130
136
|
end
|
|
131
137
|
|
|
132
138
|
# Called by InterfaceMicroservice to instantiate the Interface defined
|
|
@@ -139,6 +145,7 @@ module OpenC3
|
|
|
139
145
|
else
|
|
140
146
|
interface_or_router = klass.new
|
|
141
147
|
end
|
|
148
|
+
interface_or_router.secrets.setup(@secrets)
|
|
142
149
|
interface_or_router.target_names = @target_names.dup
|
|
143
150
|
interface_or_router.cmd_target_names = @cmd_target_names.dup
|
|
144
151
|
interface_or_router.tlm_target_names = @tlm_target_names.dup
|
|
@@ -149,6 +156,11 @@ module OpenC3
|
|
|
149
156
|
@options.each do |option|
|
|
150
157
|
interface_or_router.set_option(option[0], option[1..-1])
|
|
151
158
|
end
|
|
159
|
+
@secret_options.each do |option|
|
|
160
|
+
secret_name = option[1]
|
|
161
|
+
secret_value = interface_or_router.secrets.get(secret_name, scope: @scope)
|
|
162
|
+
interface_or_router.set_option(option[0], [secret_value])
|
|
163
|
+
end
|
|
152
164
|
@protocols.each do |protocol|
|
|
153
165
|
klass = OpenC3.require_class(protocol[1])
|
|
154
166
|
interface_or_router.add_protocol(klass, protocol[2..-1], protocol[0].upcase.intern)
|
|
@@ -168,11 +180,13 @@ module OpenC3
|
|
|
168
180
|
'reconnect_delay' => @reconnect_delay,
|
|
169
181
|
'disable_disconnect' => @disable_disconnect,
|
|
170
182
|
'options' => @options,
|
|
183
|
+
'secret_options' => @secret_options,
|
|
171
184
|
'protocols' => @protocols,
|
|
172
185
|
'log' => @log,
|
|
173
186
|
'log_raw' => @log_raw,
|
|
174
187
|
'plugin' => @plugin,
|
|
175
188
|
'needs_dependencies' => @needs_dependencies,
|
|
189
|
+
'secrets' => @secrets.as_json(*a),
|
|
176
190
|
'updated_at' => @updated_at
|
|
177
191
|
}
|
|
178
192
|
end
|
|
@@ -242,6 +256,14 @@ module OpenC3
|
|
|
242
256
|
parser.verify_num_parameters(0, 0, "#{keyword}")
|
|
243
257
|
@log_raw = true
|
|
244
258
|
|
|
259
|
+
when 'SECRET'
|
|
260
|
+
parser.verify_num_parameters(3, 4, "#{keyword} <Secret Type: ENV or FILE> <Secret Name> <Environment Variable Name or File Path> <Option Name (Optional)>")
|
|
261
|
+
@secrets << parameters[0..2]
|
|
262
|
+
if parameters[3]
|
|
263
|
+
# Option Name, Secret Name
|
|
264
|
+
@secret_options << [parameters[3], parameters[1]]
|
|
265
|
+
end
|
|
266
|
+
|
|
245
267
|
else
|
|
246
268
|
raise ConfigParser::Error.new(parser, "Unknown keyword and parameters for Interface/Router: #{keyword} #{parameters.join(" ")}")
|
|
247
269
|
|
|
@@ -261,6 +283,7 @@ module OpenC3
|
|
|
261
283
|
target_names: @target_names,
|
|
262
284
|
plugin: @plugin,
|
|
263
285
|
needs_dependencies: @needs_dependencies,
|
|
286
|
+
secrets: @secrets,
|
|
264
287
|
scope: @scope
|
|
265
288
|
)
|
|
266
289
|
unless validate_only
|
|
@@ -39,6 +39,7 @@ module OpenC3
|
|
|
39
39
|
attr_accessor :work_dir
|
|
40
40
|
attr_accessor :ports
|
|
41
41
|
attr_accessor :parent
|
|
42
|
+
attr_accessor :secrets
|
|
42
43
|
|
|
43
44
|
# NOTE: The following three class methods are used by the ModelController
|
|
44
45
|
# and are reimplemented to enable various Model class methods to work
|
|
@@ -96,6 +97,7 @@ module OpenC3
|
|
|
96
97
|
updated_at: nil,
|
|
97
98
|
plugin: nil,
|
|
98
99
|
needs_dependencies: false,
|
|
100
|
+
secrets: [],
|
|
99
101
|
scope:
|
|
100
102
|
)
|
|
101
103
|
parts = name.split("__")
|
|
@@ -118,6 +120,7 @@ module OpenC3
|
|
|
118
120
|
@parent = parent
|
|
119
121
|
@container = container
|
|
120
122
|
@needs_dependencies = needs_dependencies
|
|
123
|
+
@secrets = secrets
|
|
121
124
|
@bucket = Bucket.getClient()
|
|
122
125
|
end
|
|
123
126
|
|
|
@@ -137,6 +140,7 @@ module OpenC3
|
|
|
137
140
|
'updated_at' => @updated_at,
|
|
138
141
|
'plugin' => @plugin,
|
|
139
142
|
'needs_dependencies' => @needs_dependencies,
|
|
143
|
+
'secrets' => @secrets.as_json(*a)
|
|
140
144
|
}
|
|
141
145
|
end
|
|
142
146
|
|
|
@@ -182,6 +186,9 @@ module OpenC3
|
|
|
182
186
|
when 'CONTAINER'
|
|
183
187
|
parser.verify_num_parameters(1, 1, "#{keyword} <Container Image Name>")
|
|
184
188
|
@container = parameters[0]
|
|
189
|
+
when 'SECRET'
|
|
190
|
+
parser.verify_num_parameters(3, 3, "#{keyword} <Secret Type: ENV or FILE> <Secret Name> <Environment Variable Name or File Path>")
|
|
191
|
+
@secrets << parameters.dup
|
|
185
192
|
else
|
|
186
193
|
raise ConfigParser::Error.new(parser, "Unknown keyword and parameters for Microservice: #{keyword} #{parameters.join(" ")}")
|
|
187
194
|
end
|
data/lib/openc3/models/model.rb
CHANGED
|
@@ -154,7 +154,7 @@ module OpenC3
|
|
|
154
154
|
end
|
|
155
155
|
end
|
|
156
156
|
@updated_at = Time.now.to_nsec_from_epoch
|
|
157
|
-
self.class.store.hset(@primary_key, @name, JSON.generate(self.as_json(:allow_nan => true)))
|
|
157
|
+
self.class.store.hset(@primary_key, @name, JSON.generate(self.as_json(:allow_nan => true), :allow_nan => true))
|
|
158
158
|
end
|
|
159
159
|
|
|
160
160
|
# Alias for create(update: true)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# encoding: ascii-8bit
|
|
2
|
+
|
|
3
|
+
# Copyright 2022 OpenC3, Inc.
|
|
4
|
+
# All Rights Reserved.
|
|
5
|
+
#
|
|
6
|
+
# This program is free software; you can modify and/or redistribute it
|
|
7
|
+
# under the terms of the GNU Affero General Public License
|
|
8
|
+
# as published by the Free Software Foundation; version 3 with
|
|
9
|
+
# attribution addendums as found in the LICENSE.txt
|
|
10
|
+
#
|
|
11
|
+
# This program is distributed in the hope that it will be useful,
|
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
14
|
+
# GNU Affero General Public License for more details.
|
|
15
|
+
|
|
16
|
+
# This file may also be used under the terms of a commercial license
|
|
17
|
+
# if purchased from OpenC3, Inc.
|
|
18
|
+
|
|
19
|
+
require 'openc3/models/model'
|
|
20
|
+
|
|
21
|
+
module OpenC3
|
|
22
|
+
class SecretModel < Model
|
|
23
|
+
PRIMARY_KEY = 'openc3__secrets'
|
|
24
|
+
|
|
25
|
+
# NOTE: The following three class methods are used by the ModelController
|
|
26
|
+
# and are reimplemented to enable various Model class methods to work
|
|
27
|
+
def self.get(name:, scope:)
|
|
28
|
+
super("#{scope}__#{PRIMARY_KEY}", name: name)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def self.names(scope:)
|
|
32
|
+
super("#{scope}__#{PRIMARY_KEY}")
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def self.all(scope:)
|
|
36
|
+
super("#{scope}__#{PRIMARY_KEY}")
|
|
37
|
+
end
|
|
38
|
+
# END NOTE
|
|
39
|
+
|
|
40
|
+
def initialize(name:, value:, scope:)
|
|
41
|
+
super("#{scope}__#{PRIMARY_KEY}", name: name, scope: scope)
|
|
42
|
+
@value = value
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# @return [Hash] JSON encoding of this model
|
|
46
|
+
def as_json(*a)
|
|
47
|
+
{
|
|
48
|
+
'name' => @name,
|
|
49
|
+
'value' => @value.as_json(*a),
|
|
50
|
+
}
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|