MuranoCLI 3.0.0 → 3.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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)
|