MuranoCLI 3.0.0 → 3.0.1
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/.rubocop.yml +50 -27
- data/.trustme.vim +12 -8
- data/bin/murano +23 -8
- data/docs/basic_example.rst +1 -1
- data/docs/completions/murano_completion-bash +88 -0
- data/lib/MrMurano/Account.rb +3 -3
- data/lib/MrMurano/Business.rb +6 -6
- data/lib/MrMurano/Config-Migrate.rb +1 -3
- data/lib/MrMurano/Config.rb +16 -8
- data/lib/MrMurano/Content.rb +56 -45
- data/lib/MrMurano/Gateway.rb +62 -21
- data/lib/MrMurano/Mock.rb +27 -19
- data/lib/MrMurano/Passwords.rb +7 -7
- data/lib/MrMurano/ReCommander.rb +171 -28
- data/lib/MrMurano/Setting.rb +38 -40
- data/lib/MrMurano/Solution-ServiceConfig.rb +2 -1
- data/lib/MrMurano/Solution-Services.rb +196 -61
- data/lib/MrMurano/Solution-Users.rb +7 -7
- data/lib/MrMurano/Solution.rb +22 -8
- data/lib/MrMurano/SolutionId.rb +10 -4
- data/lib/MrMurano/SubCmdGroupContext.rb +14 -5
- data/lib/MrMurano/SyncAllowed.rb +42 -0
- data/lib/MrMurano/SyncUpDown.rb +122 -65
- data/lib/MrMurano/Webservice-Cors.rb +9 -3
- data/lib/MrMurano/Webservice-Endpoint.rb +39 -33
- data/lib/MrMurano/Webservice-File.rb +35 -24
- data/lib/MrMurano/commands/business.rb +18 -18
- data/lib/MrMurano/commands/content.rb +3 -2
- data/lib/MrMurano/commands/devices.rb +137 -22
- data/lib/MrMurano/commands/globals.rb +8 -2
- data/lib/MrMurano/commands/keystore.rb +3 -2
- data/lib/MrMurano/commands/link.rb +13 -13
- data/lib/MrMurano/commands/login.rb +3 -2
- data/lib/MrMurano/commands/mock.rb +4 -3
- data/lib/MrMurano/commands/password.rb +4 -2
- data/lib/MrMurano/commands/postgresql.rb +5 -3
- data/lib/MrMurano/commands/settings.rb +78 -62
- data/lib/MrMurano/commands/show.rb +79 -74
- data/lib/MrMurano/commands/solution.rb +6 -4
- data/lib/MrMurano/commands/solution_picker.rb +5 -4
- data/lib/MrMurano/commands/status.rb +15 -4
- data/lib/MrMurano/commands/timeseries.rb +3 -2
- data/lib/MrMurano/commands/tsdb.rb +3 -2
- data/lib/MrMurano/hash.rb +6 -6
- data/lib/MrMurano/http.rb +66 -67
- data/lib/MrMurano/makePretty.rb +18 -12
- data/lib/MrMurano/progress.rb +9 -2
- data/lib/MrMurano/verbosing.rb +14 -2
- data/lib/MrMurano/version.rb +2 -2
- data/spec/GatewayDevice_spec.rb +190 -149
- data/spec/Mock_spec.rb +3 -3
- data/spec/Solution-ServiceEventHandler_spec.rb +170 -137
- data/spec/SyncUpDown_spec.rb +205 -191
- metadata +3 -2
data/lib/MrMurano/Passwords.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Last Modified: 2017.08.
|
1
|
+
# Last Modified: 2017.08.23 /coding: utf-8
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
# Copyright © 2016-2017 Exosite LLC.
|
@@ -32,12 +32,6 @@ module MrMurano
|
|
32
32
|
return unless @path.exist?
|
33
33
|
@path.chmod(0o600)
|
34
34
|
@path.open('rb') do |io|
|
35
|
-
# 2017-07-01: Rubocop suggests using safe_load.
|
36
|
-
# https://ruby-doc.org/stdlib-2.1.0/libdoc/psych/rdoc/Psych.html#method-c-load
|
37
|
-
# https://ruby-doc.org/stdlib-2.1.0/libdoc/psych/rdoc/Psych.html#method-c-safe_load
|
38
|
-
# 2017-07-05: Oh, I don't think safe_load exists in Ruby 2 or 2.2...
|
39
|
-
#@data = YAML.safe_load(io)
|
40
|
-
# rubocop:disable Security/YAMLLoad
|
41
35
|
@data = YAML.load(io)
|
42
36
|
end
|
43
37
|
end
|
@@ -87,6 +81,12 @@ module MrMurano
|
|
87
81
|
return unless @data.is_a?(Hash)
|
88
82
|
hd = @data[host]
|
89
83
|
return unless !hd.nil? && hd.is_a?(Hash)
|
84
|
+
if $cfg['tool.dry']
|
85
|
+
MrMurano::Verbose.whirly_interject do
|
86
|
+
say(%(--dry: Not removing password for #{fancy_ticks("#{user}@#{host}")}))
|
87
|
+
end
|
88
|
+
return
|
89
|
+
end
|
90
90
|
@data[host].delete(user) if hd.key?(user)
|
91
91
|
end
|
92
92
|
|
data/lib/MrMurano/ReCommander.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Last Modified: 2017.08.
|
1
|
+
# Last Modified: 2017.08.23 /coding: utf-8
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
# Copyright © 2016-2017 Exosite LLC.
|
@@ -7,7 +7,6 @@
|
|
7
7
|
|
8
8
|
require 'optparse'
|
9
9
|
require 'MrMurano/optparse'
|
10
|
-
|
11
10
|
require 'MrMurano/verbosing'
|
12
11
|
|
13
12
|
module MrMurano
|
@@ -50,6 +49,8 @@ module Commander
|
|
50
49
|
# A command sets prompt_if_logged_off if it
|
51
50
|
# is okay to ask the user for their password.
|
52
51
|
attr_accessor :prompt_if_logged_off
|
52
|
+
# A command sets subcmdgrouphelp if it's help.
|
53
|
+
attr_accessor :subcmdgrouphelp
|
53
54
|
|
54
55
|
def verify_arg_count!(args, max_args=0, mandatory=[])
|
55
56
|
if !max_args.nil? && max_args.zero?
|
@@ -126,38 +127,180 @@ module Commander
|
|
126
127
|
|
127
128
|
alias old_parse_global_options parse_global_options
|
128
129
|
def parse_global_options
|
130
|
+
# User can specify, e.g., status.options, to set options for specific commands.
|
129
131
|
defopts = ($cfg["#{active_command.name}.options"] || '').split
|
130
132
|
@args.push(*defopts)
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
133
|
+
help_hack
|
134
|
+
parse_global_options_real
|
135
|
+
end
|
136
|
+
|
137
|
+
def parse_global_options_real
|
138
|
+
old_parse_global_options
|
139
|
+
rescue OptionParser::MissingArgument => err
|
140
|
+
if err.message.start_with?('missing argument:')
|
141
|
+
puts err.message
|
142
|
+
else
|
143
|
+
err_msg = MrMurano::Verbose.fancy_ticks(err.message)
|
144
|
+
MrMurano::Verbose.error(
|
145
|
+
"There was a problem interpreting the options: #{err_msg}"
|
146
|
+
)
|
147
|
+
end
|
148
|
+
exit 2
|
149
|
+
end
|
150
|
+
|
151
|
+
# 2017-08-22: Commander's help infrastructure is either really weak,
|
152
|
+
# or we did something elsewhere that seriously cripples it. In any
|
153
|
+
# case, this fixes all its quirks.
|
154
|
+
def help_hack
|
155
|
+
do_help = fix_args
|
156
|
+
show_alias_help_maybe! if do_help
|
157
|
+
end
|
158
|
+
|
159
|
+
def help_opts
|
160
|
+
%w[-h --help help].freeze
|
161
|
+
end
|
162
|
+
|
163
|
+
def vers_opts
|
164
|
+
%w[-v --version].freeze
|
165
|
+
end
|
166
|
+
|
167
|
+
def fix_args
|
168
|
+
# If `murano --help` is specified, let rb-commander handle it.
|
169
|
+
# But if `murano command --help` is specified, don't let cmdr
|
170
|
+
# handle it, otherwise it just shows the command description,
|
171
|
+
# but not any of the subcommands (our SubCmdGroupContext code).
|
172
|
+
# Note: not checking help_opts here, which includes 'help', because
|
173
|
+
# 'help' might really be a command argument (e.g., a solution name).
|
174
|
+
# Note: `murano -v`'s active_command is 'help'.
|
175
|
+
do_help = (@args & %w[-h --help]).any? || active_command.name == 'help'
|
176
|
+
if do_help
|
177
|
+
# If there are options in addition to --help, then Commander
|
178
|
+
# runs the command! So remove all options.
|
179
|
+
#
|
180
|
+
# E.g., this shows the help for usage:
|
181
|
+
# $ murano --help usage
|
182
|
+
# but if a flag is added, the command gets run, e.g.,
|
183
|
+
# $ murano usage --id 1234 --help
|
184
|
+
# runs the usage command.
|
185
|
+
#
|
186
|
+
# I [lb] walked the code and it looks like when Commander tries to
|
187
|
+
# validate the args, it doesn't like the --id flag (maybe because
|
188
|
+
# the "help" command is being used to validate, and it does not
|
189
|
+
# define any options?). Then it removes the --id switch (after the
|
190
|
+
# --help switch was previously removed) and tries the command *again*
|
191
|
+
# (in gems/commander-4.4.3/lib/commander/runner.rb, look for the
|
192
|
+
# comment, "Remove the offending args and retry"). So in the example
|
193
|
+
# given above, `murano usage --id 1234 --help`, both the `--id` flag
|
194
|
+
# and `--help` flag are moved from @args, and then `murano usage 1234`
|
195
|
+
# is executed (and args are not validated by Commander but merely passed
|
196
|
+
# to the command action, so `usage` gets args=['1234']). Whadda wreck.
|
197
|
+
reject_next = false
|
198
|
+
@args.reject! do |arg|
|
199
|
+
reject = false
|
200
|
+
if reject_next
|
201
|
+
reject = true
|
202
|
+
reject_next = false
|
203
|
+
elsif arg.start_with?('-') && !help_opts.include?(arg) && !vers_opts.include?(arg)
|
204
|
+
reject = true
|
205
|
+
# See if the next argument should also be consumed.
|
206
|
+
arg_opts = @options.select do |opt|
|
207
|
+
if arg =~ /^-[^-]/
|
208
|
+
# Single char abbrev: look for exact match.
|
209
|
+
opt[:switches].include?(arg)
|
210
|
+
else
|
211
|
+
# Long switch: Commander matches abbrevs of longs...
|
212
|
+
opt[:switches].any? { |oarg| oarg =~ /^#{arg}/ }
|
213
|
+
end
|
214
|
+
end
|
215
|
+
if arg =~ /^--/
|
216
|
+
exact = arg_opts.select do |opt|
|
217
|
+
opt[:switches].include?(arg) || opt[:switches].any? do |sw|
|
218
|
+
sw.start_with?(arg + ' ')
|
219
|
+
end
|
220
|
+
end
|
221
|
+
arg_opts = exact unless exact.empty?
|
222
|
+
end
|
223
|
+
if arg_opts.length > 1
|
224
|
+
# MAYBE/2017-08-23: Always do this check, not just for help.
|
225
|
+
ambig = MrMurano::Verbose.fancy_ticks(arg)
|
226
|
+
match = arg_opts.map do |opt|
|
227
|
+
opt[:switches].map { |sw| MrMurano::Verbose.fancy_ticks(sw) }.join('|')
|
228
|
+
end
|
229
|
+
match = match.flatten
|
230
|
+
if match.length > 1
|
231
|
+
match[-1] = "and #{match[-1]}"
|
232
|
+
end
|
233
|
+
match = match.join(', ')
|
234
|
+
MrMurano::Verbose.error("Ambiguous option: #{ambig} matches: #{match}")
|
235
|
+
exit 2
|
236
|
+
elsif arg_opts.length == 1
|
237
|
+
if arg_opts.first[:switches].any? { |opt| opt.start_with?('--') && opt.include?(' ') }
|
238
|
+
# There's a space in the --long setting, e.g., '--config KEY=VAL',
|
239
|
+
# so we know there's an argument following.
|
240
|
+
reject_next = true
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
reject
|
140
245
|
end
|
141
|
-
exit 2
|
142
246
|
end
|
247
|
+
@purargs = @args - help_opts
|
248
|
+
return do_help if active_command.name == 'help'
|
249
|
+
# Any command other than `murano help` or `murano --help`.
|
250
|
+
return do_help if !do_help || active_command.name.include?(' ')
|
251
|
+
# This is a single-word command, e.g., 'link', not 'link list',
|
252
|
+
# as in `murano link --help`, not `murano link list --help`.
|
253
|
+
# Positional parameters break Commander. E.g.,
|
254
|
+
# $ murano --help config application.id
|
255
|
+
# invalid command. Use --help for more information
|
256
|
+
# so remove any remaining --options and use the first term.
|
257
|
+
@args -= help_opts
|
258
|
+
@args = @args[0..0]
|
259
|
+
# Add back in the --help if the command is not a subcommand help.
|
260
|
+
@args.push('--help') unless active_command.subcmdgrouphelp
|
261
|
+
do_help
|
143
262
|
end
|
144
263
|
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
264
|
+
def show_alias_help_maybe!
|
265
|
+
return unless alias?(command_name_from_args) || (active_command.name == 'help' && @args.length > 1)
|
266
|
+
# Why, oh why, Commander, do you flake out on aliases?
|
267
|
+
# E.g.,
|
268
|
+
# $ murano product --help
|
269
|
+
# invalid command. Use --help for more information
|
270
|
+
# Though it sometimes work, like with:
|
271
|
+
# $ murano --help product device enable
|
272
|
+
# but only because Commander shows help for the 'device' command.
|
273
|
+
# I.e., this doesn't work: `murano product push --help`
|
274
|
+
# So we'll just roll our own help for aliases!
|
275
|
+
@args -= help_opts
|
276
|
+
cli_cmd = MrMurano::Verbose.fancy_ticks(@purargs.join(' '))
|
277
|
+
if active_command.name == 'help'
|
278
|
+
arg_cmd = @args.join(' ')
|
279
|
+
else
|
280
|
+
arg_cmd = command_name_from_args
|
281
|
+
end
|
282
|
+
mur_msg = ''
|
283
|
+
if @aliases[arg_cmd].nil?
|
284
|
+
matches = @aliases.keys.find_all { |key| key.start_with?('arg_cmd') }
|
285
|
+
matches = @aliases.keys.find_all { |key| key.start_with?(@args[0]) } if matches.empty?
|
286
|
+
unless matches.empty?
|
287
|
+
matches = matches.map { |match| MrMurano::Verbose.fancy_ticks(match) }
|
288
|
+
matches = matches.sort.join(', ')
|
289
|
+
mur_msg = %(The #{cli_cmd} command includes: #{matches})
|
290
|
+
end
|
291
|
+
else
|
292
|
+
mur_cmd = []
|
293
|
+
mur_cmd += [active_command.name] if active_command.name != 'help'
|
294
|
+
mur_cmd += @aliases[arg_cmd] unless @aliases[arg_cmd].empty?
|
295
|
+
mur_cmd = mur_cmd.join(' ')
|
296
|
+
#mur_cmd = active_command.name if mur_cmd.empty?
|
297
|
+
mur_cmd = MrMurano::Verbose.fancy_ticks(mur_cmd)
|
298
|
+
mur_msg = %(The #{cli_cmd} command is really: #{mur_cmd})
|
299
|
+
end
|
300
|
+
return if mur_msg.empty?
|
301
|
+
puts mur_msg
|
302
|
+
exit 0
|
303
|
+
end
|
161
304
|
end
|
162
305
|
end
|
163
306
|
|
data/lib/MrMurano/Setting.rb
CHANGED
@@ -1,3 +1,10 @@
|
|
1
|
+
# Last Modified: 2017.08.20 /coding: utf-8
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
# Copyright © 2016-2017 Exosite LLC.
|
5
|
+
# License: MIT. See LICENSE.txt.
|
6
|
+
# vim:tw=0:ts=2:sw=2:et:ai
|
7
|
+
|
1
8
|
require 'MrMurano/verbosing'
|
2
9
|
|
3
10
|
module MrMurano
|
@@ -18,50 +25,41 @@ module MrMurano
|
|
18
25
|
def mapservice(service)
|
19
26
|
service = service.to_s.downcase
|
20
27
|
SERVICE_MAP.each_pair do |k, v|
|
21
|
-
|
22
|
-
return v
|
23
|
-
end
|
28
|
+
return v if [k.downcase, v.downcase].include? service
|
24
29
|
end
|
25
|
-
|
30
|
+
# rubocop:disable Style/PerlBackrefs: "Avoid the use of Perl-style backrefs."
|
31
|
+
# Because of the upcase, we cannot just call, e.g.,
|
32
|
+
# service.sub(/(.)(.*)/, "\\1\\2")
|
33
|
+
service.sub(/(.)(.*)/) { "#{$1.upcase}#{$2.downcase}" }
|
26
34
|
end
|
27
35
|
|
28
36
|
def read(service, setting)
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
error "No Settings on \"#{service}\""
|
41
|
-
if $cfg['tool.debug'] then
|
42
|
-
error e.message
|
43
|
-
error e.to_s
|
44
|
-
end
|
37
|
+
debug %(Looking up class "MrMurano::#{mapservice(service)}::Settings")
|
38
|
+
gb = Object.const_get("MrMurano::#{mapservice(service)}::Settings").new
|
39
|
+
meth = setting.to_sym
|
40
|
+
debug %(Looking up method "#{meth}")
|
41
|
+
return gb.__send__(meth) if gb.respond_to?(meth)
|
42
|
+
error "Unknown setting '#{setting}' on '#{service}'"
|
43
|
+
rescue NameError => e
|
44
|
+
error %(No Settings on "#{service}")
|
45
|
+
if $cfg['tool.debug']
|
46
|
+
error e.message
|
47
|
+
error e.to_s
|
45
48
|
end
|
46
49
|
end
|
47
50
|
|
48
51
|
def write(service, setting, value)
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
error %(No Settings on "#{service}")
|
61
|
-
if $cfg['tool.debug'] then
|
62
|
-
error e.message
|
63
|
-
error e.to_s
|
64
|
-
end
|
52
|
+
debug %(Looking up class "MrMurano::#{mapservice(service)}::Settings")
|
53
|
+
gb = Object.const_get("MrMurano::#{mapservice(service)}::Settings").new
|
54
|
+
meth = "#{setting}=".to_sym
|
55
|
+
debug %(Looking up method "#{meth}")
|
56
|
+
return gb.__send__(meth, value) if gb.respond_to? meth
|
57
|
+
error "Unknown setting '#{setting}' on '#{service}'"
|
58
|
+
rescue NameError => e
|
59
|
+
error %(No Settings on "#{service}")
|
60
|
+
if $cfg['tool.debug']
|
61
|
+
error e.message
|
62
|
+
error e.to_s
|
65
63
|
end
|
66
64
|
end
|
67
65
|
|
@@ -74,9 +72,11 @@ module MrMurano
|
|
74
72
|
result = {}
|
75
73
|
::MrMurano.constants.each do |maybe|
|
76
74
|
begin
|
77
|
-
gb = Object
|
78
|
-
result[maybe] = gb.instance_methods(false).
|
75
|
+
gb = Object.const_get("MrMurano::#{maybe}::Settings")
|
76
|
+
result[maybe] = gb.instance_methods(false).reject { |i| i.to_s[-1] == '=' }
|
77
|
+
# rubocop:disable Lint/HandleExceptions: Do not suppress exceptions."
|
79
78
|
rescue
|
79
|
+
# EXPLAIN/2017-08-20: When/Why does this happen?
|
80
80
|
end
|
81
81
|
end
|
82
82
|
result
|
@@ -86,5 +86,3 @@ module MrMurano
|
|
86
86
|
end
|
87
87
|
end
|
88
88
|
|
89
|
-
# vim: set ai et sw=2 ts=2 :
|
90
|
-
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# Last Modified: 2017.08.
|
1
|
+
# Last Modified: 2017.08.22 /coding: utf-8
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
# Copyright © 2016-2017 Exosite LLC.
|
@@ -70,6 +70,7 @@ module MrMurano
|
|
70
70
|
end
|
71
71
|
|
72
72
|
def remove(id)
|
73
|
+
return unless remove_item_allowed(id)
|
73
74
|
delete("/#{id}")
|
74
75
|
end
|
75
76
|
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# Last Modified: 2017.08.
|
1
|
+
# Last Modified: 2017.08.23 /coding: utf-8
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
# Copyright © 2016-2017 Exosite LLC.
|
@@ -54,6 +54,7 @@ module MrMurano
|
|
54
54
|
|
55
55
|
# ??? remove
|
56
56
|
def remove(name)
|
57
|
+
return unless remove_item_allowed(name)
|
57
58
|
delete('/' + name)
|
58
59
|
end
|
59
60
|
|
@@ -69,6 +70,9 @@ module MrMurano
|
|
69
70
|
raise 'no file' unless thereitem.script
|
70
71
|
script = thereitem.script
|
71
72
|
end
|
73
|
+
|
74
|
+
script = config_vars_decode(script)
|
75
|
+
|
72
76
|
localpath = Pathname.new(localpath) unless localpath.is_a?(Pathname)
|
73
77
|
name = mkname(thereitem)
|
74
78
|
pst = thereitem.to_h.merge(
|
@@ -79,10 +83,18 @@ module MrMurano
|
|
79
83
|
name: name,
|
80
84
|
)
|
81
85
|
debug "f: #{localpath} >> #{pst.reject { |k, _| k == :script }.to_json}"
|
86
|
+
therealias = mkalias(thereitem)
|
87
|
+
upload_script(therealias, name, localpath, pst)
|
88
|
+
end
|
89
|
+
|
90
|
+
def upload_script(therealias, name, localpath, pst)
|
82
91
|
# Try PUT. If 404, then POST.
|
83
92
|
# I.e., PUT if not exists, else POST to create.
|
84
93
|
updated_at = nil
|
85
|
-
|
94
|
+
|
95
|
+
return unless upload_item_allowed(therealias)
|
96
|
+
|
97
|
+
put('/' + therealias, pst) do |request, http|
|
86
98
|
response = http.request(request)
|
87
99
|
isj, jsn = isJSON(response.body)
|
88
100
|
# ORDER: An HTTPNoContent is also a HTTPSuccess, so the latter comes first.
|
@@ -181,8 +193,6 @@ module MrMurano
|
|
181
193
|
cache_file = $cfg.file_at(cache_file_name)
|
182
194
|
if cache_file.file?
|
183
195
|
cache_file.open('r+') do |io|
|
184
|
-
# FIXME/2017-07-02: "Security/YAMLLoad: Prefer using YAML.safe_load over YAML.load."
|
185
|
-
# rubocop:disable Security/YAMLLoad
|
186
196
|
cache = YAML.load(io)
|
187
197
|
cache = {} unless cache
|
188
198
|
io.rewind
|
@@ -205,7 +215,6 @@ module MrMurano
|
|
205
215
|
return nil unless cache_file.file?
|
206
216
|
ret = nil
|
207
217
|
cache_file.open('r') do |io|
|
208
|
-
# FIXME/2017-07-02: "Security/YAMLLoad: Prefer using YAML.safe_load over YAML.load."
|
209
218
|
cache = YAML.load(io)
|
210
219
|
return nil unless cache
|
211
220
|
if cache.key?(local_path.to_s)
|
@@ -250,7 +259,6 @@ module MrMurano
|
|
250
259
|
end
|
251
260
|
|
252
261
|
def initialize(sid=nil)
|
253
|
-
# FIXME/VERIFY/2017-07-02: Check that products do not have Modules.
|
254
262
|
@solntype = 'application.id'
|
255
263
|
super
|
256
264
|
@uriparts << :module
|
@@ -307,14 +315,14 @@ module MrMurano
|
|
307
315
|
root = root.expand_path
|
308
316
|
if path.basename.sub(/\.lua$/i, '').to_s.include?('.')
|
309
317
|
warning(
|
310
|
-
"WARNING: Do not use periods in filenames. Rename:
|
318
|
+
"WARNING: Do not use periods in filenames. Rename: #{fancy_ticks(path.basename)}"
|
311
319
|
)
|
312
320
|
end
|
313
321
|
path.dirname.ascend do |ancestor|
|
314
322
|
break if ancestor == root
|
315
323
|
if ancestor.basename.to_s.include?('.')
|
316
324
|
warning(
|
317
|
-
"WARNING: Do not use periods in directory names. Rename:
|
325
|
+
"WARNING: Do not use periods in directory names. Rename: #{fancy_ticks(ancestor.basename)}"
|
318
326
|
)
|
319
327
|
end
|
320
328
|
end
|
@@ -349,7 +357,12 @@ module MrMurano
|
|
349
357
|
attr_accessor :event
|
350
358
|
# @return [String] For device2 events, the type of event.
|
351
359
|
attr_accessor :type
|
360
|
+
# @return [String] Service alias, e.g., "{product.id}"
|
361
|
+
attr_accessor :svc_alias
|
352
362
|
# @return [Boolean] True if local phantom item via eventhandler.undeletable.
|
363
|
+
# I.e., the file does not exist locally and is the
|
364
|
+
# empty string on the server, so locally considered
|
365
|
+
# empty string, too.
|
353
366
|
attr_accessor :phantom
|
354
367
|
end
|
355
368
|
|
@@ -373,6 +386,10 @@ module MrMurano
|
|
373
386
|
[remote[:service], remote[:event]].join('_')
|
374
387
|
end
|
375
388
|
|
389
|
+
def synckey(item)
|
390
|
+
"#{item[:service]}_#{item[:event]}"
|
391
|
+
end
|
392
|
+
|
376
393
|
def list(call=nil, data=nil, &block)
|
377
394
|
ret = get(call, data, &block)
|
378
395
|
return [] unless ret.is_a?(Hash) && !ret.key?(:error)
|
@@ -386,9 +403,36 @@ module MrMurano
|
|
386
403
|
toss
|
387
404
|
end
|
388
405
|
items.map { |item| EventHandlerItem.new(item) }
|
389
|
-
|
390
|
-
|
391
|
-
|
406
|
+
# MAYBE/2017-08-17:
|
407
|
+
# items.map! ...
|
408
|
+
# sort_by_name(items)
|
409
|
+
end
|
410
|
+
|
411
|
+
def resolve_config_var_usage!(there, local)
|
412
|
+
# Substitute '{product.id}' for the actual product.id if a corresponding
|
413
|
+
# local file does not exist, or if the local file already uses the alias.
|
414
|
+
encode_items = {}
|
415
|
+
local.each do |item|
|
416
|
+
encode_items[item.service] = {} if encode_items[item.service].nil?
|
417
|
+
encode_items[item.service][item.event] = item.svc_alias
|
418
|
+
end
|
419
|
+
there.map! do |item|
|
420
|
+
if (
|
421
|
+
!encode_items[item.service].nil? &&
|
422
|
+
!encode_items[item.service][item.event].nil?
|
423
|
+
)
|
424
|
+
svc_alias = encode_items[item.service][item.event]
|
425
|
+
else
|
426
|
+
# There's no corresponding local item.
|
427
|
+
_encoded, svc_alias = encode_config_var(item.service)
|
428
|
+
end
|
429
|
+
svc_alias = nil if svc_alias == item.service
|
430
|
+
unless svc_alias.nil?
|
431
|
+
item.svc_alias = svc_alias
|
432
|
+
item.name.sub!($cfg[svc_alias], "{#{svc_alias}}")
|
433
|
+
end
|
434
|
+
item
|
435
|
+
end
|
392
436
|
end
|
393
437
|
|
394
438
|
def skip?(item, skiplist)
|
@@ -400,15 +444,19 @@ module MrMurano
|
|
400
444
|
|
401
445
|
def cmp_svc_evt(item, svc_evt)
|
402
446
|
service, event = svc_evt.split('.', 2)
|
447
|
+
svc_match = (
|
448
|
+
service == item[:service] ||
|
449
|
+
(!item[:svc_alias].to_s.empty? && service == item[:svc_alias])
|
450
|
+
)
|
403
451
|
if event.nil? || item[:event] == '*'
|
404
|
-
|
452
|
+
svc_match
|
405
453
|
else
|
406
454
|
# rubocop:disable Style/IfInsideElse
|
407
455
|
#svc_evt == "#{item[:service]}.#{item[:event]}"
|
408
456
|
if service == '*'
|
409
457
|
event == item[:event]
|
410
458
|
else
|
411
|
-
|
459
|
+
svc_match && event == item[:event]
|
412
460
|
end
|
413
461
|
end
|
414
462
|
end
|
@@ -462,35 +510,33 @@ module MrMurano
|
|
462
510
|
# @match_header finds a service and an event string, e.g., "--EVENT svc evt\n"
|
463
511
|
md = @match_header.match(line)
|
464
512
|
if !md.nil?
|
465
|
-
|
466
|
-
|
467
|
-
#
|
468
|
-
|
469
|
-
# You can test this code via:
|
470
|
-
# rspec ./spec/cmd_syncdown_spec.rb
|
471
|
-
# which has a fixture with device2.data_in specified.
|
472
|
-
# QUESTION: Does writing device2.event do anything?
|
473
|
-
# You cannot edit that handler from the web UI...
|
474
|
-
# - Should we change the test?
|
475
|
-
# - Should we get rid of this device2 hack?
|
476
|
-
if md[:service] == 'device2'
|
477
|
-
event_event = 'event'
|
478
|
-
event_type = md[:event]
|
479
|
-
# FIXME/CONFIRM/2017-07-02: 'data_in' was the old event name? It's now 'event'?
|
480
|
-
# Want this?:
|
481
|
-
# event_type = 'event' if event_type == 'data_in'
|
482
|
-
else
|
483
|
-
event_event = md[:event]
|
484
|
-
event_type = nil
|
485
|
-
end
|
486
|
-
# header line.
|
513
|
+
service, config_var = decode_config_var(md[:service])
|
514
|
+
event_event, event_type = resolve_event_type(service, md[:event])
|
515
|
+
# Header line.
|
516
|
+
svc_alias = config_var if service != md[:service]
|
487
517
|
cur = EventHandlerItem.new(
|
488
|
-
|
489
|
-
event: event_event,
|
490
|
-
type: event_type,
|
518
|
+
#name
|
491
519
|
local_path: path,
|
492
|
-
|
520
|
+
#id
|
493
521
|
script: line,
|
522
|
+
line: lineno,
|
523
|
+
#line_end
|
524
|
+
#diff
|
525
|
+
#selected
|
526
|
+
#synckey
|
527
|
+
#synctype
|
528
|
+
#type
|
529
|
+
#updated_at
|
530
|
+
#dup_count
|
531
|
+
#alias
|
532
|
+
#updated_at
|
533
|
+
#created_at
|
534
|
+
#solution_id
|
535
|
+
service: service,
|
536
|
+
event: event_event,
|
537
|
+
type: event_type,
|
538
|
+
#phantom
|
539
|
+
svc_alias: svc_alias,
|
494
540
|
)
|
495
541
|
elsif !cur.nil? && !cur[:script].nil?
|
496
542
|
# 2017-07-02: Frozen string literal: change << to +=
|
@@ -501,24 +547,27 @@ module MrMurano
|
|
501
547
|
cur[:line_end] = lineno unless cur.nil?
|
502
548
|
|
503
549
|
# If cur is nil here, then we need to do a :legacy check.
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
550
|
+
to_remote_item_legacy_check(cur, path, from, lineno)
|
551
|
+
end
|
552
|
+
|
553
|
+
def to_remote_item_legacy_check(cur, path, from, lineno)
|
554
|
+
return cur unless cur.nil? && $project['services.legacy'].is_a?(Hash)
|
555
|
+
spath = path.relative_path_from(from)
|
556
|
+
debug "No headers: #{spath}"
|
557
|
+
service, event = $project['services.legacy'][spath.to_s]
|
558
|
+
debug "Legacy lookup #{spath} => [#{service}, #{event}]"
|
559
|
+
unless service.nil? || event.nil?
|
560
|
+
warning %(Event in #{spath} missing header, but has legacy support.)
|
561
|
+
warning %(Please add the header "--#EVENT #{service} #{event}")
|
562
|
+
cur = EventHandlerItem.new(
|
563
|
+
service: service,
|
564
|
+
event: event,
|
565
|
+
type: nil,
|
566
|
+
local_path: path,
|
567
|
+
line: 0,
|
568
|
+
line_end: lineno,
|
569
|
+
script: path.read, # FIXME: ick, fix this.
|
570
|
+
)
|
522
571
|
end
|
523
572
|
cur
|
524
573
|
end
|
@@ -529,13 +578,99 @@ module MrMurano
|
|
529
578
|
md = pattern_pattern.match(pattern)
|
530
579
|
return false if md.nil?
|
531
580
|
debug "match pattern: '#{md[:service]}' '#{md[:event]}'"
|
532
|
-
|
581
|
+
if (
|
582
|
+
!md[:service].empty? &&
|
583
|
+
!item[:service].casecmp(md[:service]).zero? &&
|
584
|
+
(item[:svc_alias].to_s.empty? || !item[:svc_alias].casecmp(md[:service]).zero?)
|
585
|
+
)
|
586
|
+
return false
|
587
|
+
end
|
533
588
|
return false unless md[:event].empty? || item[:event].casecmp(md[:event]).zero?
|
534
|
-
true # Both match (or
|
589
|
+
true # Both match (or both empty).
|
535
590
|
end
|
536
591
|
|
537
|
-
def
|
538
|
-
|
592
|
+
def config_vars_decode(script)
|
593
|
+
config_vars_translate(script, method(:decode_config_var))
|
594
|
+
end
|
595
|
+
|
596
|
+
def config_vars_encode(script)
|
597
|
+
config_vars_translate(script, method(:encode_config_var))
|
598
|
+
end
|
599
|
+
|
600
|
+
def config_vars_translate(script, codec)
|
601
|
+
new_script = ''
|
602
|
+
# Replace the service with a {config.variable} if appropriate.
|
603
|
+
script.lines.each do |line|
|
604
|
+
# @match_header finds a service and an event string, e.g., "--EVENT svc evt\n"
|
605
|
+
md = @match_header.match(line)
|
606
|
+
unless md.nil?
|
607
|
+
service, _config_var = codec.call(md[:service])
|
608
|
+
line = line.gsub(
|
609
|
+
/--#EVENT (?<service>\S+) (?<event>\S+)/,
|
610
|
+
"--#EVENT #{service} \\k<event>"
|
611
|
+
)
|
612
|
+
end
|
613
|
+
new_script += line
|
614
|
+
end
|
615
|
+
new_script
|
616
|
+
end
|
617
|
+
|
618
|
+
def decode_config_var(term)
|
619
|
+
decoded = term
|
620
|
+
config_var = nil
|
621
|
+
# Try to match svc against a {config.variable}.
|
622
|
+
mat = /^\{([^\.}]+)\.([^\.}]+)\}$/.match(term)
|
623
|
+
unless mat.nil?
|
624
|
+
# User specified a config value, e.g., "--#EVENT {product.id} event".
|
625
|
+
config_var = "#{mat[1]}.#{mat[2]}"
|
626
|
+
decoded = $cfg[config_var]
|
627
|
+
if decoded.nil?
|
628
|
+
warning "Config value not found for variable: #{fancy_ticks(term)}"
|
629
|
+
# The platform should complain about file not found,
|
630
|
+
# and we'll process everything else.
|
631
|
+
decoded = term
|
632
|
+
config_var = nil
|
633
|
+
end
|
634
|
+
end
|
635
|
+
[decoded, config_var]
|
636
|
+
end
|
637
|
+
|
638
|
+
def encode_config_var(term)
|
639
|
+
encoded = term
|
640
|
+
config_var = nil
|
641
|
+
# MEH/2017-08-22: For now, only matching against one config var.
|
642
|
+
# Maybe in the future we'd want to examine all the config vars?
|
643
|
+
# MAGIC_STRING: {config.variable} substitution.
|
644
|
+
if term == $cfg['product.id']
|
645
|
+
encoded = '{product.id}'
|
646
|
+
config_var = 'product.id'
|
647
|
+
end
|
648
|
+
[encoded, config_var]
|
649
|
+
end
|
650
|
+
|
651
|
+
def resolve_event_type(service, event)
|
652
|
+
# FIXME/2017-08-09: device2.event is now in the skiplist,
|
653
|
+
# but some tests have a "device2 data_in" script, which
|
654
|
+
# gets changed to "device2.event" here and then uploaded
|
655
|
+
# (note that skiplist does not apply to local items).
|
656
|
+
# You can test this code via:
|
657
|
+
# rspec ./spec/cmd_syncdown_spec.rb
|
658
|
+
# which has a fixture with device2.data_in specified.
|
659
|
+
# QUESTION: Does writing device2.event do anything?
|
660
|
+
# You cannot edit that handler from the web UI...
|
661
|
+
# - Should we change the test?
|
662
|
+
# - Should we get rid of this device2 hack?
|
663
|
+
if service == 'device2'
|
664
|
+
event_event = 'event'
|
665
|
+
event_type = event
|
666
|
+
# FIXME/CONFIRM/2017-07-02: 'data_in' was the old event name? It's now 'event'?
|
667
|
+
# Want this?:
|
668
|
+
# event_type = 'event' if event_type == 'data_in'
|
669
|
+
else
|
670
|
+
event_event = event
|
671
|
+
event_type = nil
|
672
|
+
end
|
673
|
+
[event_event, event_type]
|
539
674
|
end
|
540
675
|
|
541
676
|
def resurrect_undeletables(localbox, therebox)
|