openc3 5.11.3 → 5.12.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.
Potentially problematic release.
This version of openc3 might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/Gemfile +2 -2
- data/bin/openc3cli +26 -12
- data/data/config/_id_items.yaml +6 -4
- data/data/config/_id_params.yaml +9 -6
- data/data/config/_items.yaml +6 -4
- data/data/config/_params.yaml +3 -2
- data/data/config/interface_modifiers.yaml +1 -1
- data/data/config/microservice.yaml +10 -1
- data/data/config/plugins.yaml +13 -3
- data/data/config/target.yaml +9 -0
- data/data/config/target_config.yaml +12 -0
- data/data/config/tool.yaml +12 -3
- data/lib/openc3/api/api.rb +1 -1
- data/lib/openc3/api/cmd_api.rb +24 -24
- data/lib/openc3/api/config_api.rb +12 -12
- data/lib/openc3/api/limits_api.rb +4 -3
- data/lib/openc3/api/settings_api.rb +5 -2
- data/lib/openc3/api/tlm_api.rb +7 -10
- data/lib/openc3/conversions/unix_time_conversion.rb +8 -6
- data/lib/openc3/interfaces/tcpip_server_interface.rb +0 -7
- data/lib/openc3/io/json_drb.rb +3 -2
- data/lib/openc3/io/json_rpc.rb +6 -6
- data/lib/openc3/logs/buffered_packet_log_writer.rb +4 -2
- data/lib/openc3/logs/packet_log_writer.rb +22 -7
- data/lib/openc3/microservices/cleanup_microservice.rb +8 -1
- data/lib/openc3/microservices/decom_microservice.rb +1 -1
- data/lib/openc3/microservices/interface_microservice.rb +2 -2
- data/lib/openc3/microservices/microservice.rb +5 -2
- data/lib/openc3/microservices/reaction_microservice.rb +1 -0
- data/lib/openc3/microservices/timeline_microservice.rb +7 -5
- data/lib/openc3/migrations/20231022000000_tlm_viewer_config.rb +22 -0
- data/lib/openc3/models/activity_model.rb +21 -3
- data/lib/openc3/models/cvt_model.rb +2 -1
- data/lib/openc3/models/gem_model.rb +4 -1
- data/lib/openc3/models/interface_model.rb +11 -5
- data/lib/openc3/models/metadata_model.rb +11 -0
- data/lib/openc3/models/microservice_model.rb +16 -3
- data/lib/openc3/models/model.rb +18 -0
- data/lib/openc3/models/note_model.rb +11 -0
- data/lib/openc3/models/plugin_model.rb +18 -0
- data/lib/openc3/models/python_package_model.rb +104 -0
- data/lib/openc3/models/scope_model.rb +2 -0
- data/lib/openc3/models/sorted_model.rb +17 -8
- data/lib/openc3/models/target_model.rb +53 -18
- data/lib/openc3/models/tool_config_model.rb +9 -3
- data/lib/openc3/models/tool_model.rb +22 -7
- data/lib/openc3/models/widget_model.rb +19 -3
- data/lib/openc3/operators/microservice_operator.rb +2 -0
- data/lib/openc3/packets/limits.rb +6 -18
- data/lib/openc3/packets/packet.rb +1 -0
- data/lib/openc3/packets/parsers/format_string_parser.rb +4 -4
- data/lib/openc3/packets/parsers/limits_parser.rb +4 -4
- data/lib/openc3/packets/parsers/limits_response_parser.rb +5 -5
- data/lib/openc3/packets/parsers/processor_parser.rb +4 -4
- data/lib/openc3/packets/parsers/state_parser.rb +3 -3
- data/lib/openc3/script/api_shared.rb +50 -32
- data/lib/openc3/script/calendar.rb +109 -0
- data/lib/openc3/script/commands.rb +1 -8
- data/lib/openc3/script/{gems.rb → packages.rb} +20 -16
- data/lib/openc3/script/script.rb +49 -38
- data/lib/openc3/system/system.rb +2 -0
- data/lib/openc3/system/target.rb +10 -1
- data/lib/openc3/top_level.rb +2 -2
- data/lib/openc3/utilities/aws_bucket.rb +3 -2
- data/lib/openc3/utilities/bucket_file_cache.rb +1 -1
- data/lib/openc3/utilities/local_mode.rb +3 -1
- data/lib/openc3/utilities/logger.rb +1 -1
- data/lib/openc3/utilities/ruby_lex_utils.rb +0 -8
- data/lib/openc3/version.rb +6 -6
- data/templates/tool_angular/package.json +14 -14
- data/templates/tool_angular/yarn.lock +282 -129
- data/templates/tool_react/package.json +11 -11
- data/templates/tool_react/yarn.lock +353 -300
- data/templates/tool_svelte/package.json +12 -12
- data/templates/tool_svelte/src/services/openc3-api.js +16 -60
- data/templates/tool_svelte/yarn.lock +338 -212
- data/templates/tool_vue/package.json +9 -9
- data/templates/tool_vue/yarn.lock +55 -41
- data/templates/widget/package.json +10 -10
- data/templates/widget/yarn.lock +59 -45
- metadata +36 -5
data/lib/openc3/models/model.rb
CHANGED
@@ -182,6 +182,24 @@ module OpenC3
|
|
182
182
|
'plugin' => @plugin,
|
183
183
|
'scope' => @scope }
|
184
184
|
end
|
185
|
+
|
186
|
+
def check_disable_erb(filename)
|
187
|
+
erb_disabled = false
|
188
|
+
if @disable_erb
|
189
|
+
if @disable_erb.length == 0
|
190
|
+
# Disable all ERB
|
191
|
+
erb_disabled = true
|
192
|
+
else
|
193
|
+
@disable_erb.each do |pattern|
|
194
|
+
if filename =~ Regexp.new(pattern)
|
195
|
+
erb_disabled = true
|
196
|
+
break
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
return erb_disabled
|
202
|
+
end
|
185
203
|
end
|
186
204
|
|
187
205
|
class EphemeralModel < Model
|
@@ -33,6 +33,17 @@ module OpenC3
|
|
33
33
|
"#{scope}#{PRIMARY_KEY}"
|
34
34
|
end
|
35
35
|
|
36
|
+
def self.notify(scope:, kind:, start:, stop: nil)
|
37
|
+
json = {'type' => NOTE_TYPE, 'start' => start}
|
38
|
+
json['stop'] = stop if stop
|
39
|
+
notification = {
|
40
|
+
'data' => JSON.generate(json),
|
41
|
+
'kind' => kind,
|
42
|
+
'type' => 'calendar',
|
43
|
+
}
|
44
|
+
CalendarTopic.write_entry(notification, scope: scope)
|
45
|
+
end
|
46
|
+
|
36
47
|
attr_reader :stop, :color, :description, :type
|
37
48
|
|
38
49
|
# @param [String] scope - OpenC3 scope to track event to
|
@@ -34,6 +34,7 @@ require 'openc3/models/router_model'
|
|
34
34
|
require 'openc3/models/tool_model'
|
35
35
|
require 'openc3/models/widget_model'
|
36
36
|
require 'openc3/models/microservice_model'
|
37
|
+
require 'openc3/api/api'
|
37
38
|
require 'tmpdir'
|
38
39
|
require 'tempfile'
|
39
40
|
require 'fileutils'
|
@@ -43,6 +44,8 @@ module OpenC3
|
|
43
44
|
# microservices and tools. The PluginModel installs all these pieces as well
|
44
45
|
# as destroys them all when the plugin is removed.
|
45
46
|
class PluginModel < Model
|
47
|
+
include Api
|
48
|
+
|
46
49
|
PRIMARY_KEY = 'openc3_plugins'
|
47
50
|
# Reserved VARIABLE names. See local_mode.rb: update_local_plugin()
|
48
51
|
RESERVED_VARIABLE_NAMES = ['target_name', 'microservice_name', 'scope']
|
@@ -170,6 +173,21 @@ module OpenC3
|
|
170
173
|
end
|
171
174
|
needs_dependencies = pkg.spec.runtime_dependencies.length > 0
|
172
175
|
needs_dependencies = true if Dir.exist?(File.join(gem_path, 'lib'))
|
176
|
+
|
177
|
+
# Handle python requirements.txt
|
178
|
+
if File.exist?(File.join(gem_path, 'requirements.txt'))
|
179
|
+
begin
|
180
|
+
pypi_url = get_setting('pypi_url', scope: scope)
|
181
|
+
rescue
|
182
|
+
# If Redis isn't running try the ENV, then simply pypi.org/simple
|
183
|
+
pypi_url = ENV['PYPI_URL']
|
184
|
+
pypi_url ||= 'https://pypi.org/simple'
|
185
|
+
end
|
186
|
+
Logger.info "Installing python packages from requirements.txt"
|
187
|
+
puts `pip install --user -i #{pypi_url} -r #{File.join(gem_path, 'requirements.txt')}`
|
188
|
+
needs_dependencies = true
|
189
|
+
end
|
190
|
+
|
173
191
|
# If needs_dependencies hasn't already been set we need to scan the plugin.txt
|
174
192
|
# to see if they've explicitly set the NEEDS_DEPENDENCIES keyword
|
175
193
|
unless needs_dependencies
|
@@ -0,0 +1,104 @@
|
|
1
|
+
# encoding: ascii-8bit
|
2
|
+
|
3
|
+
# Copyright 2023 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 'fileutils'
|
20
|
+
require 'openc3/utilities/process_manager'
|
21
|
+
require 'pathname'
|
22
|
+
|
23
|
+
module OpenC3
|
24
|
+
# This class acts like a Model but doesn't inherit from Model because it doesn't
|
25
|
+
# actual interact with the Store (Redis). Instead we implement names, get, put
|
26
|
+
# and destroy to allow interaction with python package files from the PluginModel and
|
27
|
+
# the PackagesController.
|
28
|
+
class PythonPackageModel
|
29
|
+
def self.names
|
30
|
+
paths = Dir.glob("#{ENV['PYTHONUSERBASE']}/lib/*")
|
31
|
+
results = []
|
32
|
+
paths.each do |path|
|
33
|
+
results.concat(Pathname.new(File.join(path, 'site-packages')).children.select { |c| c.directory? and File.extname(c) == '.dist-info' }.collect { |p| File.basename(p, '.dist-info') })
|
34
|
+
end
|
35
|
+
return results.sort
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.get(name)
|
39
|
+
path = "#{ENV['PYTHONUSERBASE']}/cache"
|
40
|
+
FileUtils.mkdir_p(path) unless Dir.exist?(path)
|
41
|
+
result = Pathname.new(path).children.select { |c| c.file? and File.basename(c, File.extname(c)) == name }
|
42
|
+
if result.length > 0
|
43
|
+
return result[0] if File.exist?(result[0])
|
44
|
+
end
|
45
|
+
raise "Package #{name} not found"
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.put(package_file_path, package_install: true, scope:)
|
49
|
+
if File.file?(package_file_path)
|
50
|
+
package_filename = File.basename(package_file_path)
|
51
|
+
FileUtils.mkdir_p("#{ENV['PYTHONUSERBASE']}/cache") unless Dir.exist?("#{ENV['PYTHONUSERBASE']}/cache")
|
52
|
+
cache_path = "#{ENV['PYTHONUSERBASE']}/cache/#{File.basename(package_file_path)}"
|
53
|
+
FileUtils.cp(package_file_path, cache_path)
|
54
|
+
if package_install
|
55
|
+
return self.install(cache_path, scope: scope)
|
56
|
+
end
|
57
|
+
else
|
58
|
+
message = "Package file #{package_file_path} does not exist!"
|
59
|
+
Logger.error message
|
60
|
+
raise message
|
61
|
+
end
|
62
|
+
return nil
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.install(name_or_path, scope:)
|
66
|
+
if File.exist?(name_or_path)
|
67
|
+
package_file_path = name_or_path
|
68
|
+
else
|
69
|
+
package_file_path = get(name_or_path)
|
70
|
+
end
|
71
|
+
package_filename = File.basename(package_file_path)
|
72
|
+
begin
|
73
|
+
pypi_url = get_setting('pypi_url', scope: scope)
|
74
|
+
rescue
|
75
|
+
# If Redis isn't running try the ENV, then simply pypi.org/simple
|
76
|
+
pypi_url = ENV['PYPI_URL']
|
77
|
+
pypi_url ||= 'https://pypi.org/simple'
|
78
|
+
end
|
79
|
+
Logger.info "Installing python package: #{name_or_path}"
|
80
|
+
result = OpenC3::ProcessManager.instance.spawn(["pip", "install", "--user", "-i", pypi_url, package_file_path], "package_install", package_filename, Time.now + 3600.0, scope: scope)
|
81
|
+
return result.name
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.destroy(name, scope:)
|
85
|
+
package_name, version = self.extract_name_and_version(name)
|
86
|
+
Logger.info "Uninstalling package: #{name}"
|
87
|
+
result = OpenC3::ProcessManager.instance.spawn(["pip", "uninstall", package_name, "-y"], "package_uninstall", name, Time.now + 3600.0, scope: scope)
|
88
|
+
return result.name
|
89
|
+
end
|
90
|
+
|
91
|
+
def self.extract_name_and_version(name)
|
92
|
+
split_name = name.split('-')
|
93
|
+
if split_name.length > 1
|
94
|
+
package_name = split_name[0..-2].join('-')
|
95
|
+
version = File.basename(split_name[-1], '.dist-info')
|
96
|
+
else
|
97
|
+
package_name = name
|
98
|
+
version = "Unknown"
|
99
|
+
end
|
100
|
+
|
101
|
+
return package_name, version
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -305,6 +305,8 @@ module OpenC3
|
|
305
305
|
SettingModel.set({ name: 'source_url', data: 'https://github.com/OpenC3/cosmos' }, scope: @scope) unless setting
|
306
306
|
setting = SettingModel.get(name: 'rubygems_url')
|
307
307
|
SettingModel.set({ name: 'rubygems_url', data: 'https://rubygems.org' }, scope: @scope) unless setting
|
308
|
+
setting = SettingModel.get(name: 'pypi_url')
|
309
|
+
SettingModel.set({ name: 'pypi_url', data: 'https://pypi.org/simple' }, scope: @scope) unless setting
|
308
310
|
end
|
309
311
|
end
|
310
312
|
end
|
@@ -14,10 +14,10 @@
|
|
14
14
|
# GNU Affero General Public License for more details.
|
15
15
|
|
16
16
|
# Modified by OpenC3, Inc.
|
17
|
-
# All changes Copyright
|
17
|
+
# All changes Copyright 2023, 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
|
# https://www.rubydoc.info/gems/redis/Redis/Commands/SortedSets
|
@@ -39,7 +39,12 @@ module OpenC3
|
|
39
39
|
|
40
40
|
# MUST be overriden by any subclasses
|
41
41
|
def self.pk(scope)
|
42
|
-
"#{scope}#{PRIMARY_KEY}"
|
42
|
+
return "#{scope}#{PRIMARY_KEY}"
|
43
|
+
end
|
44
|
+
|
45
|
+
# MUST be overriden by any subclasses
|
46
|
+
def self.notify(scope:, kind:, start:, stop: nil)
|
47
|
+
# Do nothing by default
|
43
48
|
end
|
44
49
|
|
45
50
|
# @return [String|nil] String of the saved json or nil if start not found
|
@@ -52,7 +57,7 @@ module OpenC3
|
|
52
57
|
# @return [Array<Hash>] Array up to the limit of the models (as Hash objects) stored under the primary key
|
53
58
|
def self.all(scope:, limit: 100)
|
54
59
|
result = Store.zrevrangebyscore(self.pk(scope), '+inf', '-inf', limit: [0, limit])
|
55
|
-
result.map { |item| JSON.parse(item, :allow_nan => true, :create_additions => true) }
|
60
|
+
return result.map { |item| JSON.parse(item, :allow_nan => true, :create_additions => true) }
|
56
61
|
end
|
57
62
|
|
58
63
|
# @return [String|nil] json or nil if metadata empty
|
@@ -71,24 +76,28 @@ module OpenC3
|
|
71
76
|
raise SortedInputError.new "start: #{start} must be before stop: #{stop}"
|
72
77
|
end
|
73
78
|
result = Store.zrangebyscore(self.pk(scope), start, stop, limit: [0, limit])
|
74
|
-
result.map { |item| JSON.parse(item, :allow_nan => true, :create_additions => true) }
|
79
|
+
return result.map { |item| JSON.parse(item, :allow_nan => true, :create_additions => true) }
|
75
80
|
end
|
76
81
|
|
77
82
|
# @return [Integer] count of the members stored under the primary key
|
78
83
|
def self.count(scope:)
|
79
|
-
Store.zcard(self.pk(scope))
|
84
|
+
return Store.zcard(self.pk(scope))
|
80
85
|
end
|
81
86
|
|
82
87
|
# Remove member from a sorted set
|
83
88
|
# @return [Integer] count of the members removed, 0 if not found
|
84
89
|
def self.destroy(scope:, start:)
|
85
|
-
Store.zremrangebyscore(self.pk(scope), start, start)
|
90
|
+
result = Store.zremrangebyscore(self.pk(scope), start, start)
|
91
|
+
self.notify(kind: 'deleted', start: start, scope: scope)
|
92
|
+
return result
|
86
93
|
end
|
87
94
|
|
88
95
|
# Remove members from min to max of the sorted set.
|
89
96
|
# @return [Integer] count of the members removed
|
90
97
|
def self.range_destroy(scope:, start:, stop:)
|
91
|
-
Store.zremrangebyscore(self.pk(scope), start, stop)
|
98
|
+
result = Store.zremrangebyscore(self.pk(scope), start, stop)
|
99
|
+
self.notify(kind: 'deleted', start: start, stop: stop, scope: scope)
|
100
|
+
return result
|
92
101
|
end
|
93
102
|
|
94
103
|
attr_reader :start
|
@@ -14,7 +14,7 @@
|
|
14
14
|
# GNU Affero General Public License for more details.
|
15
15
|
|
16
16
|
# Modified by OpenC3, Inc.
|
17
|
-
# All changes Copyright
|
17
|
+
# All changes Copyright 2023, OpenC3, Inc.
|
18
18
|
# All Rights Reserved
|
19
19
|
#
|
20
20
|
# This file may also be used under the terms of a commercial license
|
@@ -78,6 +78,7 @@ module OpenC3
|
|
78
78
|
attr_accessor :needs_dependencies
|
79
79
|
attr_accessor :target_microservices
|
80
80
|
attr_accessor :children
|
81
|
+
attr_accessor :disable_erb
|
81
82
|
|
82
83
|
# NOTE: The following three class methods are used by the ModelController
|
83
84
|
# and are reimplemented to enable various Model class methods to work
|
@@ -349,6 +350,7 @@ module OpenC3
|
|
349
350
|
target_microservices: {'REDUCER' => [[]]},
|
350
351
|
reducer_disable: false,
|
351
352
|
reducer_max_cpu_utilization: 30.0,
|
353
|
+
disable_erb: nil,
|
352
354
|
scope:
|
353
355
|
)
|
354
356
|
super("#{scope}__#{PRIMARY_KEY}", name: name, plugin: plugin, updated_at: updated_at,
|
@@ -396,6 +398,7 @@ module OpenC3
|
|
396
398
|
@target_microservices = target_microservices
|
397
399
|
@reducer_disable = reducer_disable
|
398
400
|
@reducer_max_cpu_utilization = reducer_max_cpu_utilization
|
401
|
+
@disable_erb = disable_erb
|
399
402
|
@bucket = Bucket.getClient()
|
400
403
|
@children = []
|
401
404
|
end
|
@@ -435,7 +438,8 @@ module OpenC3
|
|
435
438
|
'needs_dependencies' => @needs_dependencies,
|
436
439
|
'target_microservices' => @target_microservices.as_json(:allow_nan => true),
|
437
440
|
'reducer_disable' => @reducer_disable,
|
438
|
-
'reducer_max_cpu_utilization' => @reducer_max_cpu_utilization
|
441
|
+
'reducer_max_cpu_utilization' => @reducer_max_cpu_utilization,
|
442
|
+
'disable_erb' => @disable_erb
|
439
443
|
}
|
440
444
|
end
|
441
445
|
|
@@ -544,6 +548,12 @@ module OpenC3
|
|
544
548
|
else
|
545
549
|
raise ConfigParser::Error.new(parser, "PACKET cannot be used without a TARGET_MICROSERVICE")
|
546
550
|
end
|
551
|
+
when 'DISABLE_ERB'
|
552
|
+
# 0 to unlimited parameters
|
553
|
+
@disable_erb ||= []
|
554
|
+
if parameters
|
555
|
+
@disable_erb.concat(parameters)
|
556
|
+
end
|
547
557
|
else
|
548
558
|
raise ConfigParser::Error.new(parser, "Unknown keyword and parameters for Target: #{keyword} #{parameters.join(" ")}")
|
549
559
|
end
|
@@ -567,10 +577,13 @@ module OpenC3
|
|
567
577
|
# Load target files
|
568
578
|
@filename = filename # For render
|
569
579
|
data = File.read(filename, mode: "rb")
|
580
|
+
erb_disabled = check_disable_erb(filename)
|
570
581
|
begin
|
571
|
-
|
572
|
-
|
573
|
-
|
582
|
+
unless erb_disabled
|
583
|
+
OpenC3.set_working_dir(File.dirname(filename)) do
|
584
|
+
if ERB_EXTENSIONS.include?(File.extname(filename).downcase) and File.basename(filename)[0] != '_'
|
585
|
+
data = ERB.new(data.force_encoding("UTF-8").comment_erb(), trim_mode: "-").result(binding.set_variables(variables))
|
586
|
+
end
|
574
587
|
end
|
575
588
|
end
|
576
589
|
rescue => error
|
@@ -679,9 +692,15 @@ module OpenC3
|
|
679
692
|
path = File.join(File.dirname(@filename), template_name)
|
680
693
|
end
|
681
694
|
|
695
|
+
data = File.read(path, mode: "rb")
|
696
|
+
erb_disabled = check_disable_erb(path)
|
682
697
|
begin
|
683
|
-
|
684
|
-
return
|
698
|
+
if erb_disabled
|
699
|
+
return data
|
700
|
+
else
|
701
|
+
OpenC3.set_working_dir(File.dirname(path)) do
|
702
|
+
return ERB.new(data.force_encoding("UTF-8").comment_erb(), trim_mode: "-").result(b)
|
703
|
+
end
|
685
704
|
end
|
686
705
|
rescue => error
|
687
706
|
raise "ERB error parsing: #{path}: #{error.formatted}"
|
@@ -692,14 +711,14 @@ module OpenC3
|
|
692
711
|
target_files = []
|
693
712
|
Find.find(target_folder) { |file| target_files << file }
|
694
713
|
target_files.sort!
|
695
|
-
|
696
|
-
File.open(File.join(target_folder, 'target_id.txt'), 'wb') { |file| file.write(
|
714
|
+
@id = OpenC3.hash_files(target_files, nil, 'SHA256').hexdigest
|
715
|
+
File.open(File.join(target_folder, 'target_id.txt'), 'wb') { |file| file.write(@id) }
|
697
716
|
key = "#{@scope}/targets/#{@name}/target_id.txt"
|
698
|
-
@bucket.put_object(bucket: ENV['OPENC3_CONFIG_BUCKET'], key: key, body:
|
717
|
+
@bucket.put_object(bucket: ENV['OPENC3_CONFIG_BUCKET'], key: key, body: @id)
|
699
718
|
|
700
719
|
# Create target archive zip file
|
701
720
|
prefix = File.dirname(target_folder) + '/'
|
702
|
-
output_file = File.join(temp_dir, @name + '_' +
|
721
|
+
output_file = File.join(temp_dir, @name + '_' + @id + '.zip')
|
703
722
|
Zip.continue_on_exists_proc = true
|
704
723
|
Zip::File.open(output_file, Zip::File::CREATE) do |zipfile|
|
705
724
|
target_files.each do |target_file|
|
@@ -718,7 +737,7 @@ module OpenC3
|
|
718
737
|
@bucket.put_object(bucket: ENV['OPENC3_CONFIG_BUCKET'], key: bucket_key, body: file)
|
719
738
|
end
|
720
739
|
File.open(output_file, 'rb') do |file|
|
721
|
-
bucket_key = key = "#{@scope}/target_archives/#{@name}/#{@name}_#{
|
740
|
+
bucket_key = key = "#{@scope}/target_archives/#{@name}/#{@name}_#{@id}.zip"
|
722
741
|
@bucket.put_object(bucket: ENV['OPENC3_CONFIG_BUCKET'], key: bucket_key, body: file)
|
723
742
|
end
|
724
743
|
end
|
@@ -733,7 +752,6 @@ module OpenC3
|
|
733
752
|
@cmd_tlm_files = target.cmd_tlm_files
|
734
753
|
@cmd_unique_id_mode = target.cmd_unique_id_mode
|
735
754
|
@tlm_unique_id_mode = target.tlm_unique_id_mode
|
736
|
-
@id = target.id
|
737
755
|
@limits_groups = system.limits.groups.keys
|
738
756
|
update()
|
739
757
|
|
@@ -897,13 +915,21 @@ module OpenC3
|
|
897
915
|
Logger.info "Configured microservice #{microservice_name}"
|
898
916
|
end
|
899
917
|
|
900
|
-
def deploy_decom_microservice(gem_path, variables, topics, instance = nil, parent = nil)
|
918
|
+
def deploy_decom_microservice(target, gem_path, variables, topics, instance = nil, parent = nil)
|
901
919
|
microservice_name = "#{@scope}__DECOM#{instance}__#{@name}"
|
920
|
+
# Assume Ruby initially
|
921
|
+
filename = 'decom_microservice.rb'
|
922
|
+
work_dir = '/openc3/lib/openc3/microservices'
|
923
|
+
if target.language == 'python'
|
924
|
+
filename = 'decom_microservice.py'
|
925
|
+
work_dir.sub!('openc3/lib', 'openc3/python')
|
926
|
+
parent = nil
|
927
|
+
end
|
902
928
|
microservice = MicroserviceModel.new(
|
903
929
|
name: microservice_name,
|
904
930
|
folder_name: @folder_name,
|
905
|
-
cmd: [
|
906
|
-
work_dir:
|
931
|
+
cmd: [target.language, filename, microservice_name],
|
932
|
+
work_dir: work_dir,
|
907
933
|
topics: topics,
|
908
934
|
target_names: [@name],
|
909
935
|
plugin: @plugin,
|
@@ -963,7 +989,6 @@ module OpenC3
|
|
963
989
|
name: microservice_name,
|
964
990
|
cmd: ["ruby", "multi_microservice.rb", *@children],
|
965
991
|
work_dir: '/openc3/lib/openc3/microservices',
|
966
|
-
target_names: [@name],
|
967
992
|
plugin: @plugin,
|
968
993
|
scope: @scope
|
969
994
|
)
|
@@ -976,9 +1001,14 @@ module OpenC3
|
|
976
1001
|
def deploy_target_microservices(type, base_topic_list, topic_prefix)
|
977
1002
|
target_microservices = @target_microservices[type]
|
978
1003
|
if target_microservices
|
1004
|
+
# These are stand alone microservice(s) ... not part of MULTI
|
979
1005
|
if base_topic_list
|
1006
|
+
# Only create the microservice if there are topics
|
1007
|
+
# This prevents creation of DECOM with no TLM Packets (for example)
|
980
1008
|
deploy_count = 0
|
981
1009
|
all_topics = base_topic_list.dup
|
1010
|
+
|
1011
|
+
# Figure out if there are individual packets assigned to this microservice
|
982
1012
|
target_microservices.sort! {|a, b| a.length <=> b.length}
|
983
1013
|
target_microservices.each_with_index do |packet_names, index|
|
984
1014
|
topics = []
|
@@ -996,15 +1026,19 @@ module OpenC3
|
|
996
1026
|
end
|
997
1027
|
end
|
998
1028
|
end
|
1029
|
+
# If there are any topics (packets) left over that haven't been
|
1030
|
+
# explictly handled above, spawn another microservice
|
999
1031
|
if all_topics.length > 0
|
1000
1032
|
instance = nil
|
1001
1033
|
instance = deploy_count unless deploy_count == 0
|
1002
1034
|
yield all_topics, instance, nil
|
1003
1035
|
end
|
1004
1036
|
else
|
1037
|
+
# Do not spawn the microservice
|
1005
1038
|
yield nil, nil, nil
|
1006
1039
|
end
|
1007
1040
|
else
|
1041
|
+
# Not a stand alone microservice ... part of MULTI
|
1008
1042
|
yield base_topic_list, nil, @parent if not base_topic_list or base_topic_list.length > 0
|
1009
1043
|
end
|
1010
1044
|
end
|
@@ -1068,11 +1102,12 @@ module OpenC3
|
|
1068
1102
|
|
1069
1103
|
# Decommutation Microservice
|
1070
1104
|
deploy_target_microservices('DECOM', packet_topic_list, "#{@scope}__TELEMETRY__{#{@name}}") do |topics, instance, parent|
|
1071
|
-
deploy_decom_microservice(gem_path, variables, topics, instance, parent)
|
1105
|
+
deploy_decom_microservice(system.targets[@name], gem_path, variables, topics, instance, parent)
|
1072
1106
|
end
|
1073
1107
|
|
1074
1108
|
# Reducer Microservice
|
1075
1109
|
unless @reducer_disable
|
1110
|
+
# TODO: Does Reducer even need a topic list?
|
1076
1111
|
deploy_target_microservices('REDUCER', decom_topic_list, "#{@scope}__DECOM__{#{@name}}") do |topics, instance, parent|
|
1077
1112
|
deploy_reducer_microservice(gem_path, variables, topics, instance, parent)
|
1078
1113
|
end
|
@@ -24,6 +24,12 @@ require 'openc3/utilities/local_mode'
|
|
24
24
|
|
25
25
|
module OpenC3
|
26
26
|
class ToolConfigModel
|
27
|
+
def self.config_tool_names(scope: $openc3_scope)
|
28
|
+
cursor, keys = Store.scan(0, match: "#{scope}__config__*", type: 'hash', count: 100)
|
29
|
+
# Just return the tool name that is used in the other APIs
|
30
|
+
return keys.map! { |key| key.split('__')[2] }.sort
|
31
|
+
end
|
32
|
+
|
27
33
|
def self.list_configs(tool, scope: $openc3_scope)
|
28
34
|
Store.hkeys("#{scope}__config__#{tool}")
|
29
35
|
end
|
@@ -32,14 +38,14 @@ module OpenC3
|
|
32
38
|
Store.hget("#{scope}__config__#{tool}", name)
|
33
39
|
end
|
34
40
|
|
35
|
-
def self.save_config(tool, name, data,
|
41
|
+
def self.save_config(tool, name, data, local_mode: true, scope: $openc3_scope)
|
36
42
|
Store.hset("#{scope}__config__#{tool}", name, data)
|
37
43
|
LocalMode.save_tool_config(scope, tool, name, data) if local_mode
|
38
44
|
end
|
39
45
|
|
40
|
-
def self.delete_config(tool, name, scope: $openc3_scope)
|
46
|
+
def self.delete_config(tool, name, local_mode: true, scope: $openc3_scope)
|
41
47
|
Store.hdel("#{scope}__config__#{tool}", name)
|
42
|
-
LocalMode.delete_tool_config(scope, tool, name)
|
48
|
+
LocalMode.delete_tool_config(scope, tool, name) if local_mode
|
43
49
|
end
|
44
50
|
end
|
45
51
|
end
|
@@ -39,6 +39,7 @@ module OpenC3
|
|
39
39
|
attr_accessor :shown
|
40
40
|
attr_accessor :position
|
41
41
|
attr_accessor :needs_dependencies
|
42
|
+
attr_accessor :disable_erb
|
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
|
@@ -135,6 +136,7 @@ module OpenC3
|
|
135
136
|
updated_at: nil,
|
136
137
|
plugin: nil,
|
137
138
|
needs_dependencies: false,
|
139
|
+
disable_erb: nil,
|
138
140
|
scope:
|
139
141
|
)
|
140
142
|
super("#{scope}__#{PRIMARY_KEY}", name: name, plugin: plugin, updated_at: updated_at, scope: scope)
|
@@ -152,6 +154,7 @@ module OpenC3
|
|
152
154
|
@url = "/tools/#{folder_name}" unless @url
|
153
155
|
end
|
154
156
|
@needs_dependencies = needs_dependencies
|
157
|
+
@disable_erb = disable_erb
|
155
158
|
end
|
156
159
|
|
157
160
|
def create(update: false, force: false)
|
@@ -159,9 +162,11 @@ module OpenC3
|
|
159
162
|
|
160
163
|
# Make sure a tool with this folder_name doesn't already exist
|
161
164
|
unless update
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
+
if @folder_name
|
166
|
+
tools.each do |_tool_name, tool|
|
167
|
+
if tool['folder_name'] == @folder_name
|
168
|
+
raise "Tool with folder_name #{@folder_name} already exists at create"
|
169
|
+
end
|
165
170
|
end
|
166
171
|
end
|
167
172
|
end
|
@@ -193,6 +198,7 @@ module OpenC3
|
|
193
198
|
'updated_at' => @updated_at,
|
194
199
|
'plugin' => @plugin,
|
195
200
|
'needs_dependencies' => @needs_dependencies,
|
201
|
+
'disable_erb' => @disable_erb
|
196
202
|
}
|
197
203
|
end
|
198
204
|
|
@@ -203,14 +209,14 @@ module OpenC3
|
|
203
209
|
@url = parameters[0]
|
204
210
|
when 'INLINE_URL'
|
205
211
|
parser.verify_num_parameters(1, 1, "INLINE_URL <URL>")
|
206
|
-
@inline_url = parameters[0]
|
212
|
+
@inline_url = ConfigParser.handle_nil(parameters[0])
|
207
213
|
when 'ICON'
|
208
214
|
parser.verify_num_parameters(1, 1, "ICON <ICON Name>")
|
209
215
|
@icon = parameters[0]
|
210
216
|
when 'WINDOW'
|
211
|
-
parser.verify_num_parameters(1, 1, "WINDOW <INLINE | IFRAME | NEW>")
|
217
|
+
parser.verify_num_parameters(1, 1, "WINDOW <INLINE | IFRAME | SAME | NEW>")
|
212
218
|
@window = parameters[0].to_s.upcase
|
213
|
-
raise ConfigParser::Error.new(parser, "Invalid WINDOW setting: #{@window}") unless ['INLINE', 'IFRAME', 'NEW'].include?(@window)
|
219
|
+
raise ConfigParser::Error.new(parser, "Invalid WINDOW setting: #{@window}") unless ['INLINE', 'IFRAME', 'SAME', 'NEW'].include?(@window)
|
214
220
|
when 'CATEGORY'
|
215
221
|
parser.verify_num_parameters(1, 1, "CATEGORY <Category Name>")
|
216
222
|
@category = parameters[0].to_s
|
@@ -220,6 +226,12 @@ module OpenC3
|
|
220
226
|
when 'POSITION'
|
221
227
|
parser.verify_num_parameters(1, 1, "POSITION <value>")
|
222
228
|
@position = parameters[0].to_i
|
229
|
+
when 'DISABLE_ERB'
|
230
|
+
# 0 to unlimited parameters
|
231
|
+
@disable_erb ||= []
|
232
|
+
if parameters
|
233
|
+
@disable_erb.concat(parameters)
|
234
|
+
end
|
223
235
|
else
|
224
236
|
raise ConfigParser::Error.new(parser, "Unknown keyword and parameters for Tool: #{keyword} #{parameters.join(" ")}")
|
225
237
|
end
|
@@ -240,7 +252,10 @@ module OpenC3
|
|
240
252
|
|
241
253
|
# Load tool files
|
242
254
|
data = File.read(filename, mode: "rb")
|
243
|
-
|
255
|
+
erb_disabled = check_disable_erb(filename)
|
256
|
+
unless erb_disabled
|
257
|
+
data = ERB.new(data.comment_erb(), trim_mode: "-").result(binding.set_variables(variables)) if data.is_printable?
|
258
|
+
end
|
244
259
|
unless validate_only
|
245
260
|
client = Bucket.getClient()
|
246
261
|
cache_control = BucketUtilities.get_cache_control(filename)
|
@@ -35,6 +35,7 @@ module OpenC3
|
|
35
35
|
attr_accessor :filename
|
36
36
|
attr_accessor :bucket_key
|
37
37
|
attr_accessor :needs_dependencies
|
38
|
+
attr_accessor :disable_erb
|
38
39
|
|
39
40
|
# NOTE: The following three class methods are used by the ModelController
|
40
41
|
# and are reimplemented to enable various Model class methods to work
|
@@ -87,6 +88,7 @@ module OpenC3
|
|
87
88
|
plugin: nil,
|
88
89
|
label: nil,
|
89
90
|
needs_dependencies: false,
|
91
|
+
disable_erb: nil,
|
90
92
|
scope:
|
91
93
|
)
|
92
94
|
super("#{scope}__#{PRIMARY_KEY}", name: name, plugin: plugin, updated_at: updated_at, scope: scope)
|
@@ -95,6 +97,7 @@ module OpenC3
|
|
95
97
|
@bucket_key = 'widgets/' + @full_name + '/' + @filename
|
96
98
|
@label = label
|
97
99
|
@needs_dependencies = needs_dependencies
|
100
|
+
@disable_erb = disable_erb
|
98
101
|
end
|
99
102
|
|
100
103
|
def as_json(*a)
|
@@ -104,11 +107,21 @@ module OpenC3
|
|
104
107
|
'plugin' => @plugin,
|
105
108
|
'label' => @label,
|
106
109
|
'needs_dependencies' => @needs_dependencies,
|
110
|
+
'disable_erb' => @disable_erb
|
107
111
|
}
|
108
112
|
end
|
109
113
|
|
110
114
|
def handle_config(parser, keyword, parameters)
|
111
|
-
|
115
|
+
case keyword
|
116
|
+
when 'DISABLE_ERB'
|
117
|
+
# 0 to unlimited parameters
|
118
|
+
@disable_erb ||= []
|
119
|
+
if parameters
|
120
|
+
@disable_erb.concat(parameters)
|
121
|
+
end
|
122
|
+
else
|
123
|
+
raise ConfigParser::Error.new(parser, "Unknown keyword and parameters for Widget: #{keyword} #{parameters.join(" ")}")
|
124
|
+
end
|
112
125
|
end
|
113
126
|
|
114
127
|
def deploy(gem_path, variables, validate_only: false)
|
@@ -123,8 +136,11 @@ module OpenC3
|
|
123
136
|
|
124
137
|
# Load widget file
|
125
138
|
data = File.read(filename, mode: "rb")
|
126
|
-
|
127
|
-
|
139
|
+
erb_disabled = check_disable_erb(filename)
|
140
|
+
unless erb_disabled
|
141
|
+
OpenC3.set_working_dir(File.dirname(filename)) do
|
142
|
+
data = ERB.new(data.comment_erb(), trim_mode: "-").result(binding.set_variables(variables)) if data.is_printable?
|
143
|
+
end
|
128
144
|
end
|
129
145
|
unless validate_only
|
130
146
|
cache_control = BucketUtilities.get_cache_control(@filename)
|
@@ -50,8 +50,10 @@ module OpenC3
|
|
50
50
|
env = microservice_config["env"].dup
|
51
51
|
if microservice_config["needs_dependencies"]
|
52
52
|
env['GEM_HOME'] = '/gems'
|
53
|
+
env['PYTHONUSERBASE'] = '/gems/python_packages'
|
53
54
|
else
|
54
55
|
env['GEM_HOME'] = nil
|
56
|
+
env['PYTHONUSERBASE'] = nil
|
55
57
|
end
|
56
58
|
env['OPENC3_MICROSERVICE_NAME'] = microservice_name
|
57
59
|
container = microservice_config["container"]
|