MuranoCLI 3.2.0.beta.9 → 3.2.1.pre.beta.3
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 +3 -0
- data/Rakefile +5 -0
- data/dockers/README.rst +7 -0
- data/dockers/RELEASE.rst +6 -3
- data/dockers/docker-test.sh +45 -17
- data/docs/completions/murano_completion-bash +211 -86
- data/lib/MrMurano/Account.rb +72 -4
- data/lib/MrMurano/Business.rb +163 -2
- data/lib/MrMurano/Commander-Entry.rb +1 -2
- data/lib/MrMurano/Config.rb +19 -18
- data/lib/MrMurano/Content.rb +26 -19
- data/lib/MrMurano/Gateway.rb +51 -10
- data/lib/MrMurano/ReCommander.rb +1 -1
- data/lib/MrMurano/Solution-Services.rb +80 -35
- data/lib/MrMurano/Solution-Users.rb +1 -0
- data/lib/MrMurano/SyncRoot.rb +10 -3
- data/lib/MrMurano/SyncUpDown-Core.rb +47 -36
- data/lib/MrMurano/SyncUpDown-Item.rb +46 -14
- data/lib/MrMurano/SyncUpDown.rb +22 -20
- data/lib/MrMurano/Webservice-Endpoint.rb +20 -18
- data/lib/MrMurano/Webservice-File.rb +63 -20
- data/lib/MrMurano/commands/business.rb +14 -1
- data/lib/MrMurano/commands/child.rb +148 -0
- data/lib/MrMurano/commands/devices.rb +298 -149
- data/lib/MrMurano/commands/element.rb +2 -1
- data/lib/MrMurano/commands/globals.rb +3 -0
- data/lib/MrMurano/commands/network.rb +152 -33
- data/lib/MrMurano/commands/sync.rb +2 -2
- data/lib/MrMurano/commands.rb +1 -0
- data/lib/MrMurano/verbosing.rb +13 -2
- data/lib/MrMurano/version.rb +1 -1
- data/spec/Account_spec.rb +43 -11
- data/spec/Content_spec.rb +5 -3
- data/spec/GatewayBase_spec.rb +1 -1
- data/spec/GatewayDevice_spec.rb +47 -8
- data/spec/GatewayResource_spec.rb +1 -1
- data/spec/GatewaySettings_spec.rb +1 -1
- data/spec/HttpAuthed_spec.rb +17 -3
- data/spec/ProjectFile_spec.rb +59 -23
- data/spec/Setting_spec.rb +2 -1
- data/spec/Solution-ServiceConfig_spec.rb +1 -1
- data/spec/Solution-ServiceEventHandler_spec.rb +27 -20
- data/spec/Solution-ServiceModules_spec.rb +7 -5
- data/spec/Solution-UsersRoles_spec.rb +7 -1
- data/spec/Solution_spec.rb +9 -1
- data/spec/SyncRoot_spec.rb +5 -5
- data/spec/SyncUpDown_spec.rb +262 -211
- data/spec/Verbosing_spec.rb +49 -8
- data/spec/Webservice-Cors_spec.rb +10 -1
- data/spec/Webservice-Endpoint_spec.rb +84 -65
- data/spec/Webservice-File_spec.rb +16 -11
- data/spec/Webservice-Setting_spec.rb +7 -1
- data/spec/_workspace.rb +9 -0
- data/spec/cmd_business_spec.rb +5 -10
- data/spec/cmd_common.rb +67 -32
- data/spec/cmd_config_spec.rb +9 -14
- data/spec/cmd_content_spec.rb +15 -26
- data/spec/cmd_cors_spec.rb +9 -12
- data/spec/cmd_device_spec.rb +31 -45
- data/spec/cmd_domain_spec.rb +12 -10
- data/spec/cmd_element_spec.rb +18 -17
- data/spec/cmd_exchange_spec.rb +1 -4
- data/spec/cmd_init_spec.rb +56 -72
- data/spec/cmd_keystore_spec.rb +17 -26
- data/spec/cmd_link_spec.rb +13 -17
- data/spec/cmd_password_spec.rb +9 -10
- data/spec/cmd_setting_application_spec.rb +95 -68
- data/spec/cmd_setting_product_spec.rb +59 -37
- data/spec/cmd_status_spec.rb +46 -84
- data/spec/cmd_syncdown_application_spec.rb +28 -50
- data/spec/cmd_syncdown_both_spec.rb +44 -93
- data/spec/cmd_syncdown_unit_spec.rb +858 -0
- data/spec/cmd_syncup_spec.rb +21 -56
- data/spec/cmd_token_spec.rb +0 -3
- data/spec/cmd_usage_spec.rb +15 -10
- data/spec/dry_run_formatter.rb +1 -0
- data/spec/fixtures/dumped_config +4 -4
- data/spec/spec_helper.rb +3 -0
- metadata +4 -2
data/lib/MrMurano/Gateway.rb
CHANGED
|
@@ -17,6 +17,7 @@ require 'MrMurano/Config'
|
|
|
17
17
|
require 'MrMurano/SolutionId'
|
|
18
18
|
require 'MrMurano/SyncRoot'
|
|
19
19
|
require 'MrMurano/SyncUpDown'
|
|
20
|
+
require 'MrMurano/SyncUpDown-Item'
|
|
20
21
|
|
|
21
22
|
module MrMurano
|
|
22
23
|
## The details of talking to the Gateway [Device2] service.
|
|
@@ -114,6 +115,33 @@ module MrMurano
|
|
|
114
115
|
class Resources < GweBase
|
|
115
116
|
include SyncUpDown
|
|
116
117
|
|
|
118
|
+
class GatewayItem < Item
|
|
119
|
+
# @return [String] The type of data stored in aliases for this resource.
|
|
120
|
+
# (string|boolean|number)
|
|
121
|
+
attr_accessor :format
|
|
122
|
+
# @return [String] Helpful unit description for the alias.
|
|
123
|
+
attr_accessor :unit
|
|
124
|
+
# @return [Boolean] List of data format validations.
|
|
125
|
+
attr_accessor :settable
|
|
126
|
+
# @return [Array] True if the cloud can write to this.
|
|
127
|
+
attr_accessor :allowed
|
|
128
|
+
# @return [String] The resource alias.
|
|
129
|
+
attr_accessor :alias
|
|
130
|
+
|
|
131
|
+
def reject_ephemeral!
|
|
132
|
+
super.reject do |attr_key, _|
|
|
133
|
+
[
|
|
134
|
+
# Server-siders:
|
|
135
|
+
# :format,
|
|
136
|
+
# :unit,
|
|
137
|
+
# :settable,
|
|
138
|
+
# :allowed,
|
|
139
|
+
# :alias,
|
|
140
|
+
].include? attr_key
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
117
145
|
def initialize
|
|
118
146
|
super
|
|
119
147
|
@itemkey = :alias
|
|
@@ -132,7 +160,7 @@ module MrMurano
|
|
|
132
160
|
# convert hash to array.
|
|
133
161
|
res = []
|
|
134
162
|
ret[:resources].each_pair do |key, value|
|
|
135
|
-
res << value.merge(alias: key.to_s)
|
|
163
|
+
res << GatewayItem.new(value.merge(alias: key.to_s))
|
|
136
164
|
end
|
|
137
165
|
res
|
|
138
166
|
# MAYBE/2017-08-17:
|
|
@@ -144,7 +172,8 @@ module MrMurano
|
|
|
144
172
|
res = {}
|
|
145
173
|
data.each do |value|
|
|
146
174
|
key = value[:alias]
|
|
147
|
-
|
|
175
|
+
# (lb): Item has reject method, but convert to Hash, for patch.
|
|
176
|
+
res[key] = value.to_h.reject { |k, _v| k == :alias }
|
|
148
177
|
end
|
|
149
178
|
|
|
150
179
|
patch('', resources: res)
|
|
@@ -194,8 +223,6 @@ module MrMurano
|
|
|
194
223
|
|
|
195
224
|
def syncdown_before
|
|
196
225
|
super
|
|
197
|
-
# TEST/2017-07-02: Could there be duplicate gateway items?
|
|
198
|
-
# [lb] just added code to SyncUpDown.locallist and is curious.
|
|
199
226
|
@here = locallist
|
|
200
227
|
end
|
|
201
228
|
|
|
@@ -247,10 +274,11 @@ module MrMurano
|
|
|
247
274
|
def resources_write(file_path)
|
|
248
275
|
num_synced = 0
|
|
249
276
|
# User can blow away specs/ directory if they want; we'll just make
|
|
250
|
-
# a new one. [This code somewhat copy-paste from make_directory.]
|
|
277
|
+
# a new one. [This code is somewhat copy-paste from make_directory.]
|
|
251
278
|
basedir = file_path
|
|
252
279
|
basedir = basedir.dirname unless basedir.extname.empty?
|
|
253
|
-
|
|
280
|
+
bad_basedir = basedir.to_s.empty? || basedir == File::SEPARATOR
|
|
281
|
+
raise 'Unexpected: bad basedir' if bad_basedir
|
|
254
282
|
|
|
255
283
|
unless basedir.exist?
|
|
256
284
|
if $cfg['tool.dry']
|
|
@@ -277,7 +305,7 @@ module MrMurano
|
|
|
277
305
|
@here.each do |value|
|
|
278
306
|
key = value[:alias]
|
|
279
307
|
res[key] = Hash.transform_keys_to_strings(
|
|
280
|
-
value.reject { |k, _v| k == :alias }
|
|
308
|
+
value.to_h.reject { |k, _v| k == :alias }
|
|
281
309
|
)
|
|
282
310
|
end
|
|
283
311
|
ohash = ordered_hash(res)
|
|
@@ -334,7 +362,9 @@ module MrMurano
|
|
|
334
362
|
|
|
335
363
|
res = []
|
|
336
364
|
here.each_pair do |key, value|
|
|
337
|
-
|
|
365
|
+
hash = Hash.transform_keys_to_symbols(value).merge(alias: key.to_s)
|
|
366
|
+
item = GatewayItem.new(hash)
|
|
367
|
+
res << item
|
|
338
368
|
end
|
|
339
369
|
|
|
340
370
|
sort_by_name(res)
|
|
@@ -398,8 +428,17 @@ module MrMurano
|
|
|
398
428
|
# @option opts [String,Pathname,IO] :key Shared secret for hash, password, token types;
|
|
399
429
|
# or public key for certificate auth type.
|
|
400
430
|
# May be string or IO/Pathname to file.
|
|
401
|
-
# @option opts [String] :type One of:
|
|
402
|
-
|
|
431
|
+
# @option opts [String] :type One of: DEVICE_AUTH_TYPES.
|
|
432
|
+
|
|
433
|
+
DEVICE_AUTH_TYPES = %i[
|
|
434
|
+
certificate
|
|
435
|
+
hash
|
|
436
|
+
password
|
|
437
|
+
signature
|
|
438
|
+
token
|
|
439
|
+
csr
|
|
440
|
+
].freeze
|
|
441
|
+
|
|
403
442
|
def enable(id, opts=nil)
|
|
404
443
|
opts = {} if opts.nil?
|
|
405
444
|
props = { auth: {}, locked: false }
|
|
@@ -421,6 +460,8 @@ module MrMurano
|
|
|
421
460
|
props[:auth][:type] = opts[:type]
|
|
422
461
|
end
|
|
423
462
|
unless opts[:key].nil?
|
|
463
|
+
# 2018-07-10: (lb): I think the `read` feature is no longer used
|
|
464
|
+
# (no callers pass in a File object any more).
|
|
424
465
|
props[:auth][:key] = opts[:key].is_a?(String) && opts[:key] || opts[:key].read
|
|
425
466
|
props[:auth][:type] = :certificate if props[:auth][:type].nil?
|
|
426
467
|
end
|
data/lib/MrMurano/ReCommander.rb
CHANGED
|
@@ -370,7 +370,7 @@ module Commander
|
|
|
370
370
|
def verify_solutions_unmanaged!
|
|
371
371
|
return if $cfg['tool.skip-managed']
|
|
372
372
|
# (lb): All @exosite.com employees are welcome behind the curtain.
|
|
373
|
-
if $cfg['user.name'] && $cfg['user.name'].end_with?(
|
|
373
|
+
if $cfg['user.name'] && $cfg['user.name'].end_with?('@exosite.com')
|
|
374
374
|
MrMurano::Verbose.verbose(
|
|
375
375
|
"Welcome behind the curtain, #{$cfg['user.name']}!"
|
|
376
376
|
)
|
|
@@ -53,13 +53,14 @@ module MrMurano
|
|
|
53
53
|
end
|
|
54
54
|
end
|
|
55
55
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
delete('/' + name)
|
|
56
|
+
def remove(itemkey)
|
|
57
|
+
return unless remove_item_allowed(itemkey)
|
|
58
|
+
delete('/' + itemkey)
|
|
60
59
|
end
|
|
61
60
|
|
|
62
|
-
def
|
|
61
|
+
def remove_or_clear(itemkey, item, modify=false)
|
|
62
|
+
# Note that item.phantom, well, it did, until we called reject_ephemeral!.
|
|
63
|
+
return unless remove_item_allowed(itemkey)
|
|
63
64
|
# This is a :phantom item, which has a default script, e.g.,
|
|
64
65
|
# item[:script] => "--#EVENT timer timer\n"
|
|
65
66
|
localpath = tolocalpath(location, item)
|
|
@@ -71,7 +72,6 @@ module MrMurano
|
|
|
71
72
|
def upload(localpath, thereitem, _modify=false)
|
|
72
73
|
localpath = Pathname.new(localpath) unless localpath.is_a?(Pathname)
|
|
73
74
|
if localpath.exist?
|
|
74
|
-
# we assume these are small enough to slurp.
|
|
75
75
|
script = localpath.read
|
|
76
76
|
else
|
|
77
77
|
# I.e., thereitem.phantom, an "undeletable" file that does not
|
|
@@ -84,18 +84,19 @@ module MrMurano
|
|
|
84
84
|
script = config_vars_decode(script)
|
|
85
85
|
|
|
86
86
|
name = mkname(thereitem)
|
|
87
|
-
|
|
87
|
+
req_body = thereitem.to_h.merge(
|
|
88
88
|
solution_id: @api_id,
|
|
89
89
|
script: script,
|
|
90
90
|
alias: mkalias(thereitem),
|
|
91
91
|
name: name,
|
|
92
92
|
)
|
|
93
|
-
|
|
93
|
+
|
|
94
|
+
debug "f: #{localpath} >> #{req_body.reject { |k, _| k == :script }.to_json}"
|
|
94
95
|
therealias = mkalias(thereitem)
|
|
95
|
-
upload_script(therealias, name, localpath,
|
|
96
|
+
upload_script(therealias, name, localpath, req_body)
|
|
96
97
|
end
|
|
97
98
|
|
|
98
|
-
def upload_script(therealias, name, localpath,
|
|
99
|
+
def upload_script(therealias, name, localpath, req_body)
|
|
99
100
|
# Try PUT. If 404, then POST.
|
|
100
101
|
# I.e., PUT if not exists, else POST to create.
|
|
101
102
|
updated_at = nil
|
|
@@ -105,7 +106,7 @@ module MrMurano
|
|
|
105
106
|
get_again = false
|
|
106
107
|
create_it = false
|
|
107
108
|
|
|
108
|
-
put('/' + therealias,
|
|
109
|
+
put('/' + therealias, req_body) do |request, http|
|
|
109
110
|
response = http.request(request)
|
|
110
111
|
isj, jsn = isJSON(response.body)
|
|
111
112
|
# ORDER: An HTTPNoContent is also a HTTPSuccess, so the latter comes first.
|
|
@@ -131,7 +132,10 @@ module MrMurano
|
|
|
131
132
|
create_it = true
|
|
132
133
|
else
|
|
133
134
|
relpath = localpath.sub(File.join(Dir.pwd, ''), '')
|
|
134
|
-
if
|
|
135
|
+
if (
|
|
136
|
+
response.is_a?(Net::HTTPBadRequest) &&
|
|
137
|
+
isj && jsn[:message] == 'Validation errors'
|
|
138
|
+
)
|
|
135
139
|
warning "Validation errors detected in #{relpath}:"
|
|
136
140
|
puts MrMurano::Pretties.makeJsonPretty(
|
|
137
141
|
jsn[:errors], Struct.new(:pretty).new(true),
|
|
@@ -141,6 +145,7 @@ module MrMurano
|
|
|
141
145
|
end
|
|
142
146
|
warning "Failed to upload: #{relpath}"
|
|
143
147
|
end
|
|
148
|
+
response
|
|
144
149
|
end
|
|
145
150
|
if get_again
|
|
146
151
|
ret = get('/' + CGI.escape(name))
|
|
@@ -150,7 +155,7 @@ module MrMurano
|
|
|
150
155
|
warning "Failed to verify updated_at: #{ret}"
|
|
151
156
|
end
|
|
152
157
|
end
|
|
153
|
-
post('/',
|
|
158
|
+
post('/', req_body) if create_it
|
|
154
159
|
cache_update_time_for(localpath, updated_at)
|
|
155
160
|
end
|
|
156
161
|
|
|
@@ -285,12 +290,20 @@ module MrMurano
|
|
|
285
290
|
class ModuleItem < Item
|
|
286
291
|
# @return [String] Internal Alias name
|
|
287
292
|
attr_accessor :alias
|
|
288
|
-
# @return [String] Timestamp when this was updated.
|
|
289
|
-
attr_accessor :updated_at
|
|
290
293
|
# @return [String] Timestamp when this was created.
|
|
291
294
|
attr_accessor :created_at
|
|
292
295
|
# @return [String] The application solution's ID.
|
|
293
296
|
attr_accessor :solution_id
|
|
297
|
+
|
|
298
|
+
def reject_ephemeral!
|
|
299
|
+
super.reject do |attr_key, _|
|
|
300
|
+
[
|
|
301
|
+
:alias,
|
|
302
|
+
:created_at,
|
|
303
|
+
:solution_id,
|
|
304
|
+
].include? attr_key
|
|
305
|
+
end
|
|
306
|
+
end
|
|
294
307
|
end
|
|
295
308
|
|
|
296
309
|
def initialize(api_id=nil)
|
|
@@ -335,13 +348,14 @@ module MrMurano
|
|
|
335
348
|
# sort_by_name(ret[:items])
|
|
336
349
|
end
|
|
337
350
|
|
|
338
|
-
def
|
|
351
|
+
def to_remote_items(root, path)
|
|
339
352
|
if $cfg['modules.no-nesting']
|
|
340
353
|
name = path.basename.to_s.sub(/\..*/, '')
|
|
341
354
|
else
|
|
342
355
|
name = remote_item_nested_name(root, path)
|
|
343
356
|
end
|
|
344
|
-
ModuleItem.new(name: name)
|
|
357
|
+
item = ModuleItem.new(name: name)
|
|
358
|
+
[item]
|
|
345
359
|
end
|
|
346
360
|
|
|
347
361
|
def remote_item_nested_name(root, path)
|
|
@@ -349,14 +363,16 @@ module MrMurano
|
|
|
349
363
|
root = root.expand_path
|
|
350
364
|
if path.basename.sub(/\.lua$/i, '').to_s.include?('.')
|
|
351
365
|
warning(
|
|
352
|
-
"WARNING: Do not use periods in filenames.
|
|
366
|
+
"WARNING: Do not use periods in filenames." +
|
|
367
|
+
" Rename: #{fancy_ticks(path.basename)}"
|
|
353
368
|
)
|
|
354
369
|
end
|
|
355
370
|
path.dirname.ascend do |ancestor|
|
|
356
371
|
break if ancestor == root
|
|
357
372
|
if ancestor.basename.to_s.include?('.')
|
|
358
373
|
warning(
|
|
359
|
-
"WARNING: Do not use periods in directory names.
|
|
374
|
+
"WARNING: Do not use periods in directory names." +
|
|
375
|
+
" Rename: #{fancy_ticks(ancestor.basename)}"
|
|
360
376
|
)
|
|
361
377
|
end
|
|
362
378
|
end
|
|
@@ -401,6 +417,24 @@ module MrMurano
|
|
|
401
417
|
attr_accessor :phantom
|
|
402
418
|
# @return [Boolean] True if a service that should not be deleted remotely.
|
|
403
419
|
attr_accessor :undeletable
|
|
420
|
+
|
|
421
|
+
def reject_ephemeral!
|
|
422
|
+
super.reject do |attr_key, attr_val|
|
|
423
|
+
[
|
|
424
|
+
# Server-siders:
|
|
425
|
+
# :alias,
|
|
426
|
+
# :service,
|
|
427
|
+
# :event,
|
|
428
|
+
# :type,
|
|
429
|
+
:updated_at,
|
|
430
|
+
:created_at,
|
|
431
|
+
:solution_id,
|
|
432
|
+
:svc_alias,
|
|
433
|
+
:phantom,
|
|
434
|
+
:undeletable,
|
|
435
|
+
].include?(attr_key) || ((attr_key == :type) && (attr_val.nil?))
|
|
436
|
+
end
|
|
437
|
+
end
|
|
404
438
|
end
|
|
405
439
|
|
|
406
440
|
def initialize(api_id=nil)
|
|
@@ -413,13 +447,15 @@ module MrMurano
|
|
|
413
447
|
end
|
|
414
448
|
|
|
415
449
|
def mkalias(remote)
|
|
416
|
-
|
|
450
|
+
missing_parts = remote.service.nil? || remote.event.nil?
|
|
451
|
+
raise "Missing parts! #{remote.to_h.to_json}" if missing_parts
|
|
417
452
|
#[$cfg[@solntype], remote[:service], remote[:event]].join('_')
|
|
418
453
|
[@api_id, remote[:service], remote[:event]].join('_')
|
|
419
454
|
end
|
|
420
455
|
|
|
421
456
|
def mkname(remote)
|
|
422
|
-
|
|
457
|
+
missing_parts = remote.service.nil? || remote.event.nil?
|
|
458
|
+
raise "Missing parts! #{remote.to_h.to_json}" if missing_parts
|
|
423
459
|
[remote[:service], remote[:event]].join('_')
|
|
424
460
|
end
|
|
425
461
|
|
|
@@ -449,10 +485,18 @@ module MrMurano
|
|
|
449
485
|
# Substitute '{product.id}' for the actual product.id if a corresponding
|
|
450
486
|
# local file does not exist, or if the local file already uses the alias.
|
|
451
487
|
encode_items = {}
|
|
488
|
+
build_svc_alias_map_from_local(encode_items, local)
|
|
489
|
+
resolve_name_svc_alias!(encode_items, there)
|
|
490
|
+
end
|
|
491
|
+
|
|
492
|
+
def build_svc_alias_map_from_local(encode_items, local)
|
|
452
493
|
local.each do |item|
|
|
453
494
|
encode_items[item.service] = {} if encode_items[item.service].nil?
|
|
454
495
|
encode_items[item.service][item.event] = item.svc_alias
|
|
455
496
|
end
|
|
497
|
+
end
|
|
498
|
+
|
|
499
|
+
def resolve_name_svc_alias!(encode_items, there)
|
|
456
500
|
there.map! do |item|
|
|
457
501
|
if (
|
|
458
502
|
!encode_items[item.service].nil? &&
|
|
@@ -536,21 +580,20 @@ module MrMurano
|
|
|
536
580
|
"#{item[:name]}.lua"
|
|
537
581
|
end
|
|
538
582
|
|
|
539
|
-
def
|
|
540
|
-
#
|
|
541
|
-
# This only finds the last event in a file.
|
|
542
|
-
# :legacy support doesn't allow for that. But that's ok.
|
|
543
|
-
path = Pathname.new(path) unless path.is_a?(Pathname)
|
|
583
|
+
def to_remote_items(from, path)
|
|
584
|
+
# Parses file which may contain multiple events. Return array of Items.
|
|
544
585
|
items = []
|
|
545
586
|
cur = nil
|
|
546
587
|
lineno = 0
|
|
588
|
+
path = Pathname.new(path) unless path.is_a?(Pathname)
|
|
547
589
|
path.readlines.each do |line|
|
|
548
590
|
lineno += 1
|
|
549
591
|
# @match_header finds a service and an event string, e.g., "--EVENT svc evt\n"
|
|
550
592
|
md = @match_header.match(line)
|
|
551
593
|
if !md.nil?
|
|
552
594
|
cur[:line_end] = lineno - 1 unless cur.nil?
|
|
553
|
-
|
|
595
|
+
header = line.strip
|
|
596
|
+
cur = to_remote_item_create(md, path, header, lineno)
|
|
554
597
|
items << cur
|
|
555
598
|
elsif !cur.nil? && !cur[:script].nil?
|
|
556
599
|
cur[:script] += line
|
|
@@ -563,22 +606,23 @@ module MrMurano
|
|
|
563
606
|
else
|
|
564
607
|
# If cur is nil here, then we need to do a :legacy check.
|
|
565
608
|
cur = to_remote_item_legacy_check(cur, path, from, lineno)
|
|
566
|
-
items << cur
|
|
609
|
+
items << cur unless cur.nil?
|
|
567
610
|
end
|
|
568
611
|
|
|
569
612
|
items
|
|
570
613
|
end
|
|
571
614
|
|
|
572
|
-
def to_remote_item_create(md, path,
|
|
615
|
+
def to_remote_item_create(md, path, header, lineno)
|
|
573
616
|
service, config_var = decode_config_var(md[:service])
|
|
574
617
|
event_event, event_type = resolve_event_type(service, md[:event])
|
|
575
|
-
|
|
618
|
+
header = config_vars_decode(header)
|
|
576
619
|
svc_alias = config_var if service != md[:service]
|
|
577
620
|
EventHandlerItem.new(
|
|
578
|
-
#
|
|
579
|
-
#
|
|
621
|
+
# Skipping: alias, updated_at, created_at, solution_id, phantom, undeletable
|
|
622
|
+
# name, id, line_end, diff, selected, synckey, synctype, updated_at, dup_count
|
|
580
623
|
local_path: path,
|
|
581
|
-
|
|
624
|
+
header: header,
|
|
625
|
+
script: '',
|
|
582
626
|
line_beg: lineno,
|
|
583
627
|
service: service,
|
|
584
628
|
event: event_event,
|
|
@@ -751,9 +795,10 @@ module MrMurano
|
|
|
751
795
|
undeletable.updated_at = nil
|
|
752
796
|
# Even if the user deletes the contents of a script,
|
|
753
797
|
# the platform still sends the magic header.
|
|
754
|
-
undeletable.
|
|
755
|
-
"--#EVENT #{therebox[key].service} #{therebox[key].event}
|
|
798
|
+
undeletable.header = (
|
|
799
|
+
"--#EVENT #{therebox[key].service} #{therebox[key].event}"
|
|
756
800
|
)
|
|
801
|
+
undeletable.script = ''
|
|
757
802
|
undeletable.local_path = Pathname.new(
|
|
758
803
|
File.join(location, tolocalname(thereitem, key))
|
|
759
804
|
)
|
data/lib/MrMurano/SyncRoot.rb
CHANGED
|
@@ -57,9 +57,16 @@ module MrMurano
|
|
|
57
57
|
|
|
58
58
|
##
|
|
59
59
|
# Remove all syncables.
|
|
60
|
-
def reset
|
|
61
|
-
|
|
62
|
-
|
|
60
|
+
def reset(syncsetypes=nil)
|
|
61
|
+
new_syncsetypes = [@syncset, @synctypes]
|
|
62
|
+
if syncsetypes.nil?
|
|
63
|
+
@syncset = []
|
|
64
|
+
@synctypes = {}
|
|
65
|
+
else
|
|
66
|
+
@syncset = syncsetypes[0]
|
|
67
|
+
@synctypes = syncsetypes[1]
|
|
68
|
+
end
|
|
69
|
+
new_syncsetypes
|
|
63
70
|
end
|
|
64
71
|
|
|
65
72
|
##
|
|
@@ -51,21 +51,28 @@ module MrMurano
|
|
|
51
51
|
tomod = dt[:tomod]
|
|
52
52
|
|
|
53
53
|
itemkey = @itemkey.to_sym
|
|
54
|
-
|
|
54
|
+
|
|
55
|
+
todel.reject { |item| item[:phantom] }.each do |item|
|
|
55
56
|
syncup_item(item, options, :delete, 'Removing') do |aitem|
|
|
56
|
-
|
|
57
|
+
remove(aitem[itemkey])
|
|
58
|
+
num_synced += 1
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
todel.select { |item| item[:phantom] }.each do |item|
|
|
62
|
+
syncup_item(item, options, :delete, 'Clearing') do |aitem|
|
|
63
|
+
remove_or_clear(aitem[itemkey], aitem.reject_ephemeral!, true)
|
|
57
64
|
num_synced += 1
|
|
58
65
|
end
|
|
59
66
|
end
|
|
60
67
|
toadd.each do |item|
|
|
61
68
|
syncup_item(item, options, :create, 'Adding') do |aitem|
|
|
62
|
-
upload(aitem[:local_path], aitem.
|
|
69
|
+
upload(aitem[:local_path], aitem.reject_ephemeral!, false)
|
|
63
70
|
num_synced += 1
|
|
64
71
|
end
|
|
65
72
|
end
|
|
66
73
|
tomod.each do |item|
|
|
67
74
|
syncup_item(item, options, :update, 'Updating') do |aitem|
|
|
68
|
-
upload(aitem[:local_path], aitem.
|
|
75
|
+
upload(aitem[:local_path], aitem.reject_ephemeral!, true)
|
|
69
76
|
num_synced += 1
|
|
70
77
|
end
|
|
71
78
|
end
|
|
@@ -163,7 +170,7 @@ module MrMurano
|
|
|
163
170
|
def dodiff(merged, local, _there=nil, options={})
|
|
164
171
|
localname = dodiff_resolve_localname(merged)
|
|
165
172
|
trmt, tlcl = dodiff_tempfile_paths(localname)
|
|
166
|
-
dodiff_header_aware(merged, trmt, tlcl, options)
|
|
173
|
+
dodiff_header_aware(merged, local, trmt, tlcl, options)
|
|
167
174
|
end
|
|
168
175
|
|
|
169
176
|
def dodiff_resolve_localname(merged)
|
|
@@ -186,10 +193,10 @@ module MrMurano
|
|
|
186
193
|
raise
|
|
187
194
|
end
|
|
188
195
|
|
|
189
|
-
def dodiff_header_aware(merged, trmt, tlcl, options)
|
|
196
|
+
def dodiff_header_aware(merged, local, trmt, tlcl, options)
|
|
190
197
|
dodiff_download_remote(merged, trmt, options)
|
|
191
198
|
MrMurano::Verbose.whirly_stop
|
|
192
|
-
dodiff_prepare_local_and_diff(merged, trmt, tlcl, options)
|
|
199
|
+
dodiff_prepare_local_and_diff(merged, local, trmt, tlcl, options)
|
|
193
200
|
ensure
|
|
194
201
|
trmt.close
|
|
195
202
|
trmt.unlink
|
|
@@ -202,13 +209,9 @@ module MrMurano
|
|
|
202
209
|
diff_download(tmp_path, merged, options)
|
|
203
210
|
end
|
|
204
211
|
|
|
205
|
-
def dodiff_prepare_local_and_diff(merged, trmt, tlcl, options)
|
|
212
|
+
def dodiff_prepare_local_and_diff(merged, local, trmt, tlcl, options)
|
|
206
213
|
cmd = dodiff_build_cmd(trmt, tlcl, options)
|
|
207
|
-
merged
|
|
208
|
-
outp = dodiff_flexible(merged, tlcl, local, cmd, use_header=false)
|
|
209
|
-
return outp if outp.to_s.empty? || !merged.key?(:script)
|
|
210
|
-
merged[:exclude_header] = false
|
|
211
|
-
dodiff_diff_dynamic(merged, tlcl, local, cmd, use_header=true)
|
|
214
|
+
dodiff_flexible(merged, local, tlcl, cmd, use_header: true)
|
|
212
215
|
end
|
|
213
216
|
|
|
214
217
|
def dodiff_build_cmd(trmt, tlcl, options)
|
|
@@ -232,18 +235,22 @@ module MrMurano
|
|
|
232
235
|
cmd
|
|
233
236
|
end
|
|
234
237
|
|
|
235
|
-
def dodiff_flexible(merged,
|
|
236
|
-
dodiff_local_to_tempfile(merged,
|
|
238
|
+
def dodiff_flexible(merged, local, tlcl, cmd, use_header: true)
|
|
239
|
+
dodiff_local_to_tempfile(merged, local, tlcl, use_header: use_header)
|
|
237
240
|
dodiff_do_diff(cmd)
|
|
238
241
|
end
|
|
239
242
|
|
|
240
|
-
def dodiff_local_to_tempfile(merged,
|
|
243
|
+
def dodiff_local_to_tempfile(merged, local, tlcl, use_header: true)
|
|
241
244
|
Pathname.new(tlcl.path).open('wb') do |io|
|
|
242
245
|
# Copy the local file to a temp file, for the diff command.
|
|
243
246
|
# And for resources, remove the local-only :selected key, as
|
|
244
247
|
# it's not part of the remote item that gets downloaded next.
|
|
245
248
|
if merged.key?(:script)
|
|
246
|
-
|
|
249
|
+
if use_header && !merged[:header].to_s.empty?
|
|
250
|
+
io << config_vars_decode(merged[:header])
|
|
251
|
+
# Add newline after the :header.
|
|
252
|
+
io.puts
|
|
253
|
+
end
|
|
247
254
|
io << config_vars_decode(merged[:script])
|
|
248
255
|
else
|
|
249
256
|
# For most items, read the local file.
|
|
@@ -379,17 +386,7 @@ module MrMurano
|
|
|
379
386
|
|
|
380
387
|
items_cull_clashes!(statuses)
|
|
381
388
|
|
|
382
|
-
|
|
383
|
-
unless options[:unselected]
|
|
384
|
-
statuses.each do |bucket, items|
|
|
385
|
-
select_selected(items)
|
|
386
|
-
next unless $cfg['tool.debug']
|
|
387
|
-
loci = items.map { |item| item.location_friendly(full_path: true) }
|
|
388
|
-
selected_paths = loci.sort.join("\n ")
|
|
389
|
-
selected_paths = (selected_paths.empty? && ' <none>' || "\n ") + selected_paths
|
|
390
|
-
debug %(#{self.class}: Selected #{bucket} items:#{selected_paths})
|
|
391
|
-
end
|
|
392
|
-
end
|
|
389
|
+
item_select_selected!(statuses, options)
|
|
393
390
|
|
|
394
391
|
statuses
|
|
395
392
|
end
|
|
@@ -631,14 +628,6 @@ module MrMurano
|
|
|
631
628
|
end
|
|
632
629
|
end
|
|
633
630
|
|
|
634
|
-
def select_selected(items)
|
|
635
|
-
items.select! { |item| item[:selected] }
|
|
636
|
-
items.map do |item|
|
|
637
|
-
item.delete(:selected)
|
|
638
|
-
item
|
|
639
|
-
end
|
|
640
|
-
end
|
|
641
|
-
|
|
642
631
|
def items_cull_clashes!(statuses)
|
|
643
632
|
clash = []
|
|
644
633
|
statuses.each_value do |items|
|
|
@@ -656,6 +645,28 @@ module MrMurano
|
|
|
656
645
|
end
|
|
657
646
|
statuses[:clash] = clash
|
|
658
647
|
end
|
|
648
|
+
|
|
649
|
+
def item_select_selected!(statuses, options)
|
|
650
|
+
# (lb): :unselected is currently only used by tests.
|
|
651
|
+
return if options[:unselected]
|
|
652
|
+
statuses.each do |bucket, items|
|
|
653
|
+
select_selected!(items)
|
|
654
|
+
debug_selected(bucket, items)
|
|
655
|
+
end
|
|
656
|
+
end
|
|
657
|
+
|
|
658
|
+
def select_selected!(items)
|
|
659
|
+
items.select! { |item| item[:selected] }
|
|
660
|
+
items.map { |item| item.delete(:selected) }
|
|
661
|
+
end
|
|
662
|
+
|
|
663
|
+
def debug_selected(bucket, items)
|
|
664
|
+
return unless $cfg['tool.debug']
|
|
665
|
+
loci = items.map { |item| item.location_friendly(full_path: true) }
|
|
666
|
+
selected_paths = loci.sort.join("\n ")
|
|
667
|
+
selected_paths = (selected_paths.empty? && ' <none>' || "\n ") + selected_paths
|
|
668
|
+
debug %(#{self.class}: Selected #{bucket} items:#{selected_paths})
|
|
669
|
+
end
|
|
659
670
|
end
|
|
660
671
|
end
|
|
661
672
|
|