openc3 5.11.3 → 5.12.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +2 -2
  3. data/bin/openc3cli +26 -12
  4. data/data/config/_id_items.yaml +6 -4
  5. data/data/config/_id_params.yaml +9 -6
  6. data/data/config/_items.yaml +6 -4
  7. data/data/config/_params.yaml +3 -2
  8. data/data/config/interface_modifiers.yaml +1 -1
  9. data/data/config/microservice.yaml +10 -1
  10. data/data/config/plugins.yaml +13 -3
  11. data/data/config/target.yaml +9 -0
  12. data/data/config/target_config.yaml +12 -0
  13. data/data/config/tool.yaml +12 -3
  14. data/lib/openc3/api/api.rb +1 -1
  15. data/lib/openc3/api/cmd_api.rb +24 -24
  16. data/lib/openc3/api/config_api.rb +12 -12
  17. data/lib/openc3/api/limits_api.rb +4 -3
  18. data/lib/openc3/api/settings_api.rb +5 -2
  19. data/lib/openc3/api/tlm_api.rb +7 -10
  20. data/lib/openc3/conversions/unix_time_conversion.rb +8 -6
  21. data/lib/openc3/interfaces/tcpip_server_interface.rb +0 -7
  22. data/lib/openc3/io/json_drb.rb +3 -2
  23. data/lib/openc3/io/json_rpc.rb +6 -6
  24. data/lib/openc3/logs/buffered_packet_log_writer.rb +4 -2
  25. data/lib/openc3/logs/packet_log_writer.rb +22 -7
  26. data/lib/openc3/microservices/cleanup_microservice.rb +8 -1
  27. data/lib/openc3/microservices/decom_microservice.rb +1 -1
  28. data/lib/openc3/microservices/interface_microservice.rb +2 -2
  29. data/lib/openc3/microservices/microservice.rb +5 -2
  30. data/lib/openc3/microservices/reaction_microservice.rb +1 -0
  31. data/lib/openc3/microservices/timeline_microservice.rb +7 -5
  32. data/lib/openc3/migrations/20231022000000_tlm_viewer_config.rb +22 -0
  33. data/lib/openc3/models/activity_model.rb +21 -3
  34. data/lib/openc3/models/cvt_model.rb +2 -1
  35. data/lib/openc3/models/gem_model.rb +4 -1
  36. data/lib/openc3/models/interface_model.rb +11 -5
  37. data/lib/openc3/models/metadata_model.rb +11 -0
  38. data/lib/openc3/models/microservice_model.rb +16 -3
  39. data/lib/openc3/models/model.rb +18 -0
  40. data/lib/openc3/models/note_model.rb +11 -0
  41. data/lib/openc3/models/plugin_model.rb +18 -0
  42. data/lib/openc3/models/python_package_model.rb +104 -0
  43. data/lib/openc3/models/scope_model.rb +2 -0
  44. data/lib/openc3/models/sorted_model.rb +17 -8
  45. data/lib/openc3/models/target_model.rb +53 -18
  46. data/lib/openc3/models/tool_config_model.rb +9 -3
  47. data/lib/openc3/models/tool_model.rb +22 -7
  48. data/lib/openc3/models/widget_model.rb +19 -3
  49. data/lib/openc3/operators/microservice_operator.rb +2 -0
  50. data/lib/openc3/packets/limits.rb +6 -18
  51. data/lib/openc3/packets/packet.rb +1 -0
  52. data/lib/openc3/packets/parsers/format_string_parser.rb +4 -4
  53. data/lib/openc3/packets/parsers/limits_parser.rb +4 -4
  54. data/lib/openc3/packets/parsers/limits_response_parser.rb +5 -5
  55. data/lib/openc3/packets/parsers/processor_parser.rb +4 -4
  56. data/lib/openc3/packets/parsers/state_parser.rb +3 -3
  57. data/lib/openc3/script/api_shared.rb +50 -32
  58. data/lib/openc3/script/calendar.rb +109 -0
  59. data/lib/openc3/script/commands.rb +1 -8
  60. data/lib/openc3/script/{gems.rb → packages.rb} +20 -16
  61. data/lib/openc3/script/script.rb +49 -38
  62. data/lib/openc3/system/system.rb +2 -0
  63. data/lib/openc3/system/target.rb +10 -1
  64. data/lib/openc3/top_level.rb +2 -2
  65. data/lib/openc3/utilities/aws_bucket.rb +3 -2
  66. data/lib/openc3/utilities/bucket_file_cache.rb +1 -1
  67. data/lib/openc3/utilities/local_mode.rb +3 -1
  68. data/lib/openc3/utilities/logger.rb +1 -1
  69. data/lib/openc3/utilities/ruby_lex_utils.rb +0 -8
  70. data/lib/openc3/version.rb +6 -6
  71. data/templates/tool_angular/package.json +14 -14
  72. data/templates/tool_angular/yarn.lock +282 -129
  73. data/templates/tool_react/package.json +11 -11
  74. data/templates/tool_react/yarn.lock +353 -300
  75. data/templates/tool_svelte/package.json +12 -12
  76. data/templates/tool_svelte/src/services/openc3-api.js +16 -60
  77. data/templates/tool_svelte/yarn.lock +338 -212
  78. data/templates/tool_vue/package.json +9 -9
  79. data/templates/tool_vue/yarn.lock +55 -41
  80. data/templates/widget/package.json +10 -10
  81. data/templates/widget/yarn.lock +59 -45
  82. metadata +36 -5
@@ -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 2022, OpenC3, Inc.
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 2022, OpenC3, Inc.
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
- OpenC3.set_working_dir(File.dirname(filename)) do
572
- if ERB_EXTENSIONS.include?(File.extname(filename).downcase) and File.basename(filename)[0] != '_'
573
- data = ERB.new(data.force_encoding("UTF-8").comment_erb(), trim_mode: "-").result(binding.set_variables(variables))
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
- OpenC3.set_working_dir(File.dirname(path)) do
684
- return ERB.new(File.read(path).force_encoding("UTF-8").comment_erb(), trim_mode: "-").result(b)
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
- hash = OpenC3.hash_files(target_files, nil, 'SHA256').hexdigest
696
- File.open(File.join(target_folder, 'target_id.txt'), 'wb') { |file| file.write(hash) }
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: hash)
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 + '_' + hash + '.zip')
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}_#{hash}.zip"
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: ["ruby", "decom_microservice.rb", microservice_name],
906
- work_dir: '/openc3/lib/openc3/microservices',
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, scope: $openc3_scope, local_mode: true)
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
- tools.each do |_tool_name, tool|
163
- if tool['folder_name'] == @folder_name
164
- raise "Tool with folder_name #{@folder_name} already exists at create"
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
- data = ERB.new(data.comment_erb(), trim_mode: "-").result(binding.set_variables(variables)) if data.is_printable?
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
- raise ConfigParser::Error.new(parser, "Unknown keyword and parameters for Widget: #{keyword} #{parameters.join(" ")}")
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
- OpenC3.set_working_dir(File.dirname(filename)) do
127
- data = ERB.new(data.comment_erb(), trim_mode: "-").result(binding.set_variables(variables)) if data.is_printable?
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"]