MuranoCLI 3.2.0.beta.9 → 3.2.1.pre.beta.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
|