MuranoCLI 3.0.7 → 3.0.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -1
- data/.ignore +1 -1
- data/.rubocop.yml +10 -5
- data/.trustme.sh +272 -106
- data/.trustme.vim +20 -1
- data/Gemfile +7 -5
- data/LICENSE.txt +14 -15
- data/MuranoCLI.gemspec +7 -5
- data/Rakefile +5 -5
- data/bin/murano +4 -4
- data/lib/MrMurano/Account.rb +13 -8
- data/lib/MrMurano/Business.rb +6 -7
- data/lib/MrMurano/Commander-Entry.rb +5 -5
- data/lib/MrMurano/Config-Migrate.rb +4 -4
- data/lib/MrMurano/Config.rb +27 -6
- data/lib/MrMurano/Content.rb +5 -5
- data/lib/MrMurano/Exchange-Element.rb +4 -4
- data/lib/MrMurano/Exchange.rb +4 -4
- data/lib/MrMurano/Gateway.rb +22 -11
- data/lib/MrMurano/Keystore.rb +4 -4
- data/lib/MrMurano/Logs.rb +87 -0
- data/lib/MrMurano/Mock.rb +5 -4
- data/lib/MrMurano/Passwords.rb +4 -5
- data/lib/MrMurano/ProjectFile.rb +4 -4
- data/lib/MrMurano/ReCommander.rb +4 -4
- data/lib/MrMurano/Setting.rb +5 -5
- data/lib/MrMurano/Settings-HttpService.rb +9 -6
- data/lib/MrMurano/Solution-ServiceConfig.rb +5 -5
- data/lib/MrMurano/Solution-Services.rb +76 -50
- data/lib/MrMurano/Solution-Users.rb +5 -4
- data/lib/MrMurano/Solution.rb +6 -6
- data/lib/MrMurano/SolutionId.rb +4 -4
- data/lib/MrMurano/SubCmdGroupContext.rb +4 -4
- data/lib/MrMurano/SyncAllowed.rb +4 -4
- data/lib/MrMurano/SyncRoot.rb +5 -5
- data/lib/MrMurano/SyncUpDown-Core.rb +515 -0
- data/lib/MrMurano/SyncUpDown-Item.rb +159 -0
- data/lib/MrMurano/SyncUpDown.rb +120 -688
- data/lib/MrMurano/Webservice-Cors.rb +4 -4
- data/lib/MrMurano/Webservice-Endpoint.rb +9 -6
- data/lib/MrMurano/Webservice-File.rb +5 -4
- data/lib/MrMurano/Webservice.rb +5 -5
- data/lib/MrMurano/commands/business.rb +4 -4
- data/lib/MrMurano/commands/completion.rb +6 -6
- data/lib/MrMurano/commands/config.rb +7 -5
- data/lib/MrMurano/commands/content.rb +5 -4
- data/lib/MrMurano/commands/cors.rb +4 -4
- data/lib/MrMurano/commands/devices.rb +6 -6
- data/lib/MrMurano/commands/domain.rb +4 -4
- data/lib/MrMurano/commands/exchange.rb +4 -4
- data/lib/MrMurano/commands/gb.rb +4 -4
- data/lib/MrMurano/commands/globals.rb +12 -4
- data/lib/MrMurano/commands/init.rb +5 -4
- data/lib/MrMurano/commands/keystore.rb +4 -4
- data/lib/MrMurano/commands/link.rb +4 -4
- data/lib/MrMurano/commands/login.rb +4 -4
- data/lib/MrMurano/commands/logs.rb +229 -76
- data/lib/MrMurano/commands/mock.rb +4 -4
- data/lib/MrMurano/commands/password.rb +4 -4
- data/lib/MrMurano/commands/postgresql.rb +4 -4
- data/lib/MrMurano/commands/settings.rb +4 -4
- data/lib/MrMurano/commands/show.rb +4 -4
- data/lib/MrMurano/commands/solution.rb +4 -4
- data/lib/MrMurano/commands/solution_picker.rb +4 -4
- data/lib/MrMurano/commands/status.rb +12 -4
- data/lib/MrMurano/commands/sync.rb +4 -4
- data/lib/MrMurano/commands/timeseries.rb +4 -4
- data/lib/MrMurano/commands/tsdb.rb +6 -7
- data/lib/MrMurano/commands/usage.rb +4 -4
- data/lib/MrMurano/commands.rb +4 -4
- data/lib/MrMurano/hash.rb +5 -5
- data/lib/MrMurano/http.rb +26 -22
- data/lib/MrMurano/makePretty.rb +194 -10
- data/lib/MrMurano/optparse.rb +1 -1
- data/lib/MrMurano/orderedhash.rb +1 -1
- data/lib/MrMurano/progress.rb +4 -4
- data/lib/MrMurano/verbosing.rb +6 -6
- data/lib/MrMurano/version.rb +5 -5
- data/lib/MrMurano.rb +7 -4
- data/spec/Account-Passwords_spec.rb +4 -4
- data/spec/Account_spec.rb +4 -4
- data/spec/Business_spec.rb +4 -4
- data/spec/ConfigFile_spec.rb +4 -4
- data/spec/ConfigMigrate_spec.rb +5 -4
- data/spec/Config_spec.rb +5 -4
- data/spec/Content_spec.rb +5 -4
- data/spec/GatewayBase_spec.rb +4 -4
- data/spec/GatewayDevice_spec.rb +4 -4
- data/spec/GatewayResource_spec.rb +5 -4
- data/spec/GatewaySettings_spec.rb +4 -4
- data/spec/Http_spec.rb +4 -4
- data/spec/MakePretties_spec.rb +20 -20
- data/spec/Mock_spec.rb +4 -4
- data/spec/ProjectFile_spec.rb +4 -4
- data/spec/Setting_spec.rb +4 -4
- data/spec/Solution-ServiceConfig_spec.rb +4 -4
- data/spec/Solution-ServiceEventHandler_spec.rb +5 -4
- data/spec/Solution-ServiceModules_spec.rb +5 -4
- data/spec/Solution-UsersRoles_spec.rb +5 -4
- data/spec/Solution_spec.rb +4 -4
- data/spec/SyncRoot_spec.rb +4 -4
- data/spec/SyncUpDown_spec.rb +67 -21
- data/spec/Verbosing_spec.rb +12 -10
- data/spec/Webservice-Cors_spec.rb +4 -4
- data/spec/Webservice-Endpoint_spec.rb +5 -4
- data/spec/Webservice-File_spec.rb +5 -4
- data/spec/Webservice-Setting_spec.rb +4 -4
- data/spec/_workspace.rb +4 -4
- data/spec/cmd_business_spec.rb +4 -5
- data/spec/cmd_common.rb +51 -20
- data/spec/cmd_config_spec.rb +4 -5
- data/spec/cmd_content_spec.rb +4 -5
- data/spec/cmd_cors_spec.rb +4 -5
- data/spec/cmd_device_spec.rb +5 -6
- data/spec/cmd_domain_spec.rb +4 -5
- data/spec/cmd_exchange_spec.rb +4 -5
- data/spec/cmd_help_spec.rb +4 -5
- data/spec/cmd_init_spec.rb +16 -35
- data/spec/cmd_keystore_spec.rb +4 -5
- data/spec/cmd_link_spec.rb +11 -12
- data/spec/cmd_logs_spec.rb +162 -0
- data/spec/cmd_password_spec.rb +4 -5
- data/spec/cmd_setting_application_spec.rb +4 -5
- data/spec/cmd_setting_product_spec.rb +4 -5
- data/spec/cmd_status_spec.rb +44 -81
- data/spec/cmd_syncdown_application_spec.rb +7 -10
- data/spec/cmd_syncdown_both_spec.rb +10 -25
- data/spec/cmd_syncup_spec.rb +31 -37
- data/spec/cmd_usage_spec.rb +4 -5
- data/spec/fixtures/dumped_config +1 -0
- data/spec/fixtures/websocket/logs_blather.rb +27 -0
- data/spec/fixtures/websocket/logs_faker.rb +153 -0
- data/spec/fixtures/websocket/simple_connection.rb +45 -0
- data/spec/fixtures/websocket/simple_options.rb +77 -0
- data/spec/fixtures/websocket/simple_server.rb +69 -0
- data/spec/fixtures/websocket/wss-echo.rb +48 -0
- data/spec/fixtures/websocket/wss-fake-logs.rb +20 -0
- metadata +55 -2
@@ -0,0 +1,159 @@
|
|
1
|
+
# Copyright © 2016-2017 Exosite LLC. All Rights Reserved
|
2
|
+
# License: PROPRIETARY. See LICENSE.txt.
|
3
|
+
# frozen_string_literal: true
|
4
|
+
|
5
|
+
# vim:tw=0:ts=2:sw=2:et:ai
|
6
|
+
# Unauthorized copying of this file is strictly prohibited.
|
7
|
+
|
8
|
+
require 'MrMurano/verbosing'
|
9
|
+
|
10
|
+
module MrMurano
|
11
|
+
## The functionality of a Syncable thing.
|
12
|
+
#
|
13
|
+
# This provides the logic for computing what things have changed, and pushing and
|
14
|
+
# pulling those things.
|
15
|
+
#
|
16
|
+
module SyncUpDown
|
17
|
+
# This is one item that can be synced.
|
18
|
+
class Item
|
19
|
+
# @return [String] The name of this item.
|
20
|
+
attr_accessor :name
|
21
|
+
# @return [Pathname] Where this item lives.
|
22
|
+
attr_accessor :local_path
|
23
|
+
# FIXME/EXPLAIN: ??? what is this?
|
24
|
+
attr_accessor :id
|
25
|
+
# @return [String] The Lua code for this item. (not all items use this.)
|
26
|
+
attr_accessor :script
|
27
|
+
# @return [Integer] The line in #local_path where this #script starts.
|
28
|
+
attr_accessor :line
|
29
|
+
# @return [Integer] The line in #local_path where this #script ends.
|
30
|
+
attr_accessor :line_end
|
31
|
+
# @return [String] If requested, the diff output.
|
32
|
+
attr_accessor :diff
|
33
|
+
# @return [Boolean] When filtering, did this item pass.
|
34
|
+
attr_accessor :selected
|
35
|
+
# @return [String] The constructed name used to match local items to remote items.
|
36
|
+
attr_accessor :synckey
|
37
|
+
# @return [String] The syncable type.
|
38
|
+
attr_accessor :synctype
|
39
|
+
# @return [String] The updated_at time from the server is used to detect changes.
|
40
|
+
attr_accessor :updated_at
|
41
|
+
# @return [Integer] Positive if multiple conflicting files found for same item.
|
42
|
+
attr_accessor :dup_count
|
43
|
+
|
44
|
+
# Initialize a new Item with a few, or all, attributes.
|
45
|
+
# @param hsh [Hash{Symbol=>Object}, Item] Initial values
|
46
|
+
#
|
47
|
+
# @example Initializing with a Hash
|
48
|
+
# Item.new(:name=>'Bob', :local_path => Pathname.new(…))
|
49
|
+
# @example Initializing with an Item
|
50
|
+
# item = Item.new(:name => 'get')
|
51
|
+
# Item.new(item)
|
52
|
+
def initialize(hsh={})
|
53
|
+
hsh.each_pair { |k, v| self[k] = v }
|
54
|
+
end
|
55
|
+
|
56
|
+
def as_inst(key)
|
57
|
+
return key if key.to_s[0] == '@'
|
58
|
+
"@#{key}"
|
59
|
+
end
|
60
|
+
private :as_inst
|
61
|
+
|
62
|
+
def as_sym(key)
|
63
|
+
return key.to_sym if key.to_s[0] != '@'
|
64
|
+
key.to_s[1..-1].to_sym
|
65
|
+
end
|
66
|
+
private :as_sym
|
67
|
+
|
68
|
+
# Get attribute as if this was a Hash
|
69
|
+
# @param key [String,Symbol] attribute name
|
70
|
+
# @return [Object] The value
|
71
|
+
def [](key)
|
72
|
+
public_send(key.to_sym)
|
73
|
+
rescue NoMethodError
|
74
|
+
nil
|
75
|
+
end
|
76
|
+
|
77
|
+
# Set attribute as if this was a Hash
|
78
|
+
# @param key [String,Symbol] attribute name
|
79
|
+
# @param value [Object] value to set
|
80
|
+
def []=(key, value)
|
81
|
+
public_send("#{key}=", value)
|
82
|
+
rescue StandardError => err
|
83
|
+
MrMurano::Verbose.error(
|
84
|
+
"Unable to set key: #{key} / value: #{value} / err: #{err} / self: #{inspect}"
|
85
|
+
)
|
86
|
+
end
|
87
|
+
|
88
|
+
# Delete a key
|
89
|
+
# @param key [String,Symbol] attribute name
|
90
|
+
# @return [Object] The value
|
91
|
+
def delete(key)
|
92
|
+
inst = as_inst(key)
|
93
|
+
remove_instance_variable(inst) if instance_variable_defined?(inst)
|
94
|
+
end
|
95
|
+
|
96
|
+
# @return [Hash{Symbol=>Object}] A hash that represents this Item
|
97
|
+
def to_h
|
98
|
+
Hash[instance_variables.map { |k| [as_sym(k), instance_variable_get(k)] }]
|
99
|
+
end
|
100
|
+
|
101
|
+
# Adds the contents of item to self.
|
102
|
+
# @param item [Item,Hash] Stuff to merge
|
103
|
+
# @return [Item] ourself
|
104
|
+
def merge!(item)
|
105
|
+
item.each_pair { |k, v| self[k] = v }
|
106
|
+
self
|
107
|
+
end
|
108
|
+
|
109
|
+
# A new Item containing our plus items.
|
110
|
+
# @param item [Item,Hash] Stuff to merge
|
111
|
+
# @return [Item] New item with contents of both
|
112
|
+
def merge(item)
|
113
|
+
dup.merge!(item)
|
114
|
+
end
|
115
|
+
|
116
|
+
# Calls block once for each non-nil key
|
117
|
+
# @yieldparam key [Symbol] The name of the key
|
118
|
+
# @yieldparam value [Object] The value for that key
|
119
|
+
# @return [Item]
|
120
|
+
def each_pair
|
121
|
+
instance_variables.each do |key|
|
122
|
+
yield as_sym(key), instance_variable_get(key)
|
123
|
+
end
|
124
|
+
self
|
125
|
+
end
|
126
|
+
|
127
|
+
# Delete items in self that block returns true.
|
128
|
+
# @yieldparam key [Symbol] The name of the key
|
129
|
+
# @yieldparam value [Object] The value for that key
|
130
|
+
# @yieldreturn [Boolean] True to delete this key
|
131
|
+
# @return [Item] Ourself.
|
132
|
+
def reject!(&_block)
|
133
|
+
instance_variables.each do |key|
|
134
|
+
drop = yield as_sym(key), instance_variable_get(key)
|
135
|
+
delete(key) if drop
|
136
|
+
end
|
137
|
+
self
|
138
|
+
end
|
139
|
+
|
140
|
+
# A new Item with keys deleted where block is true
|
141
|
+
# @yieldparam key [Symbol] The name of the key
|
142
|
+
# @yieldparam value [Object] The value for that key
|
143
|
+
# @yieldreturn [Boolean] True to delete this key
|
144
|
+
# @return [Item] New Item with keys deleted
|
145
|
+
def reject(&block)
|
146
|
+
dup.reject!(&block)
|
147
|
+
end
|
148
|
+
|
149
|
+
# For unit testing.
|
150
|
+
include Comparable
|
151
|
+
def <=>(other)
|
152
|
+
# rubocop:disable Style/RedundantSelf: Redundant self detected.
|
153
|
+
# MAYBE/2017-07-18: Permanently disable Style/RedundantSelf?
|
154
|
+
self.to_h <=> other.to_h
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
data/lib/MrMurano/SyncUpDown.rb
CHANGED
@@ -1,173 +1,27 @@
|
|
1
|
-
#
|
1
|
+
# Copyright © 2016-2017 Exosite LLC. All Rights Reserved
|
2
|
+
# License: PROPRIETARY. See LICENSE.txt.
|
2
3
|
# frozen_string_literal: true
|
3
4
|
|
4
|
-
#
|
5
|
-
#
|
6
|
-
# vim:tw=0:ts=2:sw=2:et:ai
|
7
|
-
|
8
|
-
# FIXME/MAYBE: Fix semicolon usage.
|
9
|
-
# rubocop:disable Style/Semicolon
|
5
|
+
# vim:tw=0:ts=2:sw=2:et:ai
|
6
|
+
# Unauthorized copying of this file is strictly prohibited.
|
10
7
|
|
11
8
|
require 'inflecto'
|
12
|
-
require 'open3'
|
13
9
|
require 'os'
|
14
10
|
require 'pathname'
|
15
|
-
|
16
|
-
require 'tempfile'
|
17
|
-
require 'MrMurano/progress'
|
11
|
+
require 'time'
|
18
12
|
require 'MrMurano/verbosing'
|
19
|
-
require 'MrMurano/hash'
|
20
|
-
#require 'MrMurano/Config'
|
21
|
-
#require 'MrMurano/ProjectFile'
|
22
13
|
require 'MrMurano/SyncAllowed'
|
23
|
-
|
14
|
+
require 'MrMurano/SyncUpDown-Core'
|
15
|
+
require 'MrMurano/SyncUpDown-Item'
|
24
16
|
|
25
17
|
module MrMurano
|
26
18
|
## The functionality of a Syncable thing.
|
27
19
|
#
|
28
|
-
# This provides the logic for computing what things have changed,
|
29
|
-
# pulling those things.
|
30
|
-
#
|
20
|
+
# This provides the logic for computing what things have changed,
|
21
|
+
# and pushing and pulling those things.
|
31
22
|
module SyncUpDown
|
32
23
|
include SyncAllowed
|
33
|
-
|
34
|
-
# This is one item that can be synced.
|
35
|
-
class Item
|
36
|
-
# @return [String] The name of this item.
|
37
|
-
attr_accessor :name
|
38
|
-
# @return [Pathname] Where this item lives.
|
39
|
-
attr_accessor :local_path
|
40
|
-
# FIXME/EXPLAIN: ??? what is this?
|
41
|
-
attr_accessor :id
|
42
|
-
# @return [String] The Lua code for this item. (not all items use this.)
|
43
|
-
attr_accessor :script
|
44
|
-
# @return [Integer] The line in #local_path where this #script starts.
|
45
|
-
attr_accessor :line
|
46
|
-
# @return [Integer] The line in #local_path where this #script ends.
|
47
|
-
attr_accessor :line_end
|
48
|
-
# @return [String] If requested, the diff output.
|
49
|
-
attr_accessor :diff
|
50
|
-
# @return [Boolean] When filtering, did this item pass.
|
51
|
-
attr_accessor :selected
|
52
|
-
# @return [String] The constructed name used to match local items to remote items.
|
53
|
-
attr_accessor :synckey
|
54
|
-
# @return [String] The syncable type.
|
55
|
-
attr_accessor :synctype
|
56
|
-
# @return [String] The updated_at time from the server is used to detect changes.
|
57
|
-
attr_accessor :updated_at
|
58
|
-
# @return [Integer] Positive if multiple conflicting files found for same item.
|
59
|
-
attr_accessor :dup_count
|
60
|
-
|
61
|
-
# Initialize a new Item with a few, or all, attributes.
|
62
|
-
# @param hsh [Hash{Symbol=>Object}, Item] Initial values
|
63
|
-
#
|
64
|
-
# @example Initializing with a Hash
|
65
|
-
# Item.new(:name=>'Bob', :local_path => Pathname.new(…))
|
66
|
-
# @example Initializing with an Item
|
67
|
-
# item = Item.new(:name => 'get')
|
68
|
-
# Item.new(item)
|
69
|
-
def initialize(hsh={})
|
70
|
-
hsh.each_pair { |k, v| self[k] = v }
|
71
|
-
end
|
72
|
-
|
73
|
-
def as_inst(key)
|
74
|
-
return key if key.to_s[0] == '@'
|
75
|
-
"@#{key}"
|
76
|
-
end
|
77
|
-
private :as_inst
|
78
|
-
def as_sym(key)
|
79
|
-
return key.to_sym if key.to_s[0] != '@'
|
80
|
-
key.to_s[1..-1].to_sym
|
81
|
-
end
|
82
|
-
private :as_sym
|
83
|
-
|
84
|
-
# Get attribute as if this was a Hash
|
85
|
-
# @param key [String,Symbol] attribute name
|
86
|
-
# @return [Object] The value
|
87
|
-
def [](key)
|
88
|
-
public_send(key.to_sym)
|
89
|
-
end
|
90
|
-
|
91
|
-
# Set attribute as if this was a Hash
|
92
|
-
# @param key [String,Symbol] attribute name
|
93
|
-
# @param value [Object] value to set
|
94
|
-
def []=(key, value)
|
95
|
-
public_send("#{key}=", value)
|
96
|
-
rescue StandardError => err
|
97
|
-
MrMurano::Verbose.error(
|
98
|
-
"Unable to set key: #{key} / value: #{value} / err: #{err} / self: #{inspect}"
|
99
|
-
)
|
100
|
-
end
|
101
|
-
|
102
|
-
# Delete a key
|
103
|
-
# @param key [String,Symbol] attribute name
|
104
|
-
# @return [Object] The value
|
105
|
-
def delete(key)
|
106
|
-
inst = as_inst(key)
|
107
|
-
remove_instance_variable(inst) if instance_variable_defined?(inst)
|
108
|
-
end
|
109
|
-
|
110
|
-
# @return [Hash{Symbol=>Object}] A hash that represents this Item
|
111
|
-
def to_h
|
112
|
-
Hash[instance_variables.map { |k| [as_sym(k), instance_variable_get(k)] }]
|
113
|
-
end
|
114
|
-
|
115
|
-
# Adds the contents of item to self.
|
116
|
-
# @param item [Item,Hash] Stuff to merge
|
117
|
-
# @return [Item] ourself
|
118
|
-
def merge!(item)
|
119
|
-
item.each_pair { |k, v| self[k] = v }
|
120
|
-
self
|
121
|
-
end
|
122
|
-
|
123
|
-
# A new Item containing our plus items.
|
124
|
-
# @param item [Item,Hash] Stuff to merge
|
125
|
-
# @return [Item] New item with contents of both
|
126
|
-
def merge(item)
|
127
|
-
dup.merge!(item)
|
128
|
-
end
|
129
|
-
|
130
|
-
# Calls block once for each non-nil key
|
131
|
-
# @yieldparam key [Symbol] The name of the key
|
132
|
-
# @yieldparam value [Object] The value for that key
|
133
|
-
# @return [Item]
|
134
|
-
def each_pair
|
135
|
-
instance_variables.each do |key|
|
136
|
-
yield as_sym(key), instance_variable_get(key)
|
137
|
-
end
|
138
|
-
self
|
139
|
-
end
|
140
|
-
|
141
|
-
# Delete items in self that block returns true.
|
142
|
-
# @yieldparam key [Symbol] The name of the key
|
143
|
-
# @yieldparam value [Object] The value for that key
|
144
|
-
# @yieldreturn [Boolean] True to delete this key
|
145
|
-
# @return [Item] Ourself.
|
146
|
-
def reject!(&_block)
|
147
|
-
instance_variables.each do |key|
|
148
|
-
drop = yield as_sym(key), instance_variable_get(key)
|
149
|
-
delete(key) if drop
|
150
|
-
end
|
151
|
-
self
|
152
|
-
end
|
153
|
-
|
154
|
-
# A new Item with keys deleted where block is true
|
155
|
-
# @yieldparam key [Symbol] The name of the key
|
156
|
-
# @yieldparam value [Object] The value for that key
|
157
|
-
# @yieldreturn [Boolean] True to delete this key
|
158
|
-
# @return [Item] New Item with keys deleted
|
159
|
-
def reject(&block)
|
160
|
-
dup.reject!(&block)
|
161
|
-
end
|
162
|
-
|
163
|
-
# For unit testing.
|
164
|
-
include Comparable
|
165
|
-
def <=>(other)
|
166
|
-
# rubocop:disable Style/RedundantSelf: Redundant self detected.
|
167
|
-
# MAYBE/2017-07-18: Permanently disable Style/RedundantSelf?
|
168
|
-
self.to_h <=> other.to_h
|
169
|
-
end
|
170
|
-
end # MrMurano::SyncUpDown::Item
|
24
|
+
include SyncCore
|
171
25
|
|
172
26
|
#######################################################################
|
173
27
|
# Methods that must be overridden
|
@@ -193,6 +47,10 @@ module MrMurano
|
|
193
47
|
# :nocov:
|
194
48
|
end
|
195
49
|
|
50
|
+
def remove_lite(itemkey, _thereitem, _modify=false)
|
51
|
+
remove(itemkey)
|
52
|
+
end
|
53
|
+
|
196
54
|
## Upload local item to remote
|
197
55
|
#
|
198
56
|
# Children objects Must override this
|
@@ -300,31 +158,32 @@ module MrMurano
|
|
300
158
|
#
|
301
159
|
# @param local [Pathname] Full path of where to download to
|
302
160
|
# @param item [Item] The item to download
|
303
|
-
def download(local, item, options
|
161
|
+
def download(local, item, options: {}, is_tmp: false)
|
304
162
|
#if item[:bundled]
|
305
163
|
# warning "Not downloading into bundled item #{synckey(item)}"
|
306
164
|
# return
|
307
165
|
#end
|
308
166
|
id = item[@itemkey.to_sym]
|
309
167
|
if id.to_s.empty?
|
310
|
-
# 2017-09-05: MRMUR-156: User seeing this.
|
311
168
|
if @itemkey.to_sym != :id
|
312
|
-
debug "
|
169
|
+
debug "Missing '#{@itemkey}', trying :id instead"
|
313
170
|
id = item[:id]
|
314
171
|
end
|
315
172
|
if id.to_s.empty?
|
316
|
-
debug %(
|
173
|
+
debug %(Missing id: remote: #{item[:name]} / local: #{local} / item: #{item})
|
317
174
|
return if options[:ignore_errors]
|
318
175
|
error %(Remote item missing :id => #{local})
|
319
|
-
|
176
|
+
say %(You can ignore this error using --ignore-errors)
|
320
177
|
exit 1
|
321
178
|
end
|
322
179
|
debug ":id => #{id}"
|
323
180
|
end
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
181
|
+
unless is_tmp
|
182
|
+
relpath = local.relative_path_from(Pathname.pwd).to_s
|
183
|
+
return unless download_item_allowed(relpath)
|
184
|
+
end
|
185
|
+
# MAYBE: If is_tmp and doing syncdown, just use this file rather
|
186
|
+
# than downloading again.
|
328
187
|
local.dirname.mkpath
|
329
188
|
local.open('wb') do |io|
|
330
189
|
fetch(id) do |chunk|
|
@@ -334,8 +193,8 @@ module MrMurano
|
|
334
193
|
update_mtime(local, item)
|
335
194
|
end
|
336
195
|
|
337
|
-
def diff_download(tmp_path, merged)
|
338
|
-
download(tmp_path, merged)
|
196
|
+
def diff_download(tmp_path, merged, options)
|
197
|
+
download(tmp_path, merged, options: options, is_tmp: true)
|
339
198
|
end
|
340
199
|
|
341
200
|
## Give the local file the same timestamp as the remote, because diff.
|
@@ -350,7 +209,7 @@ module MrMurano
|
|
350
209
|
return unless item[:updated_at]
|
351
210
|
|
352
211
|
mod_time = item[:updated_at]
|
353
|
-
mod_time =
|
212
|
+
mod_time = Time.parse(mod_time) unless mod_time.is_a?(Time)
|
354
213
|
begin
|
355
214
|
FileUtils.touch([local.to_path], mtime: mod_time)
|
356
215
|
rescue Errno::EACCES => err
|
@@ -362,7 +221,7 @@ module MrMurano
|
|
362
221
|
#)
|
363
222
|
unless OS.windows?
|
364
223
|
msg = 'Unexpected: touch failed on non-Windows machine'
|
365
|
-
|
224
|
+
warn "#{msg} / host_os: #{RbConfig::CONFIG['host_os']} / err: #{err}"
|
366
225
|
end
|
367
226
|
|
368
227
|
# 2017-07-13: Nor does ctime work.
|
@@ -420,6 +279,7 @@ module MrMurano
|
|
420
279
|
end
|
421
280
|
|
422
281
|
def syncup_after
|
282
|
+
0
|
423
283
|
end
|
424
284
|
|
425
285
|
def syncdown_before
|
@@ -427,6 +287,7 @@ module MrMurano
|
|
427
287
|
end
|
428
288
|
|
429
289
|
def syncdown_after(_local)
|
290
|
+
0
|
430
291
|
end
|
431
292
|
|
432
293
|
def diff_item_write(io, merged, _local, _remote)
|
@@ -453,89 +314,107 @@ module MrMurano
|
|
453
314
|
# This collects items in the project and all bundles.
|
454
315
|
# @return [Array<Item>] items found
|
455
316
|
#
|
456
|
-
# 2017-07-02: [lb] removed this commented-out code from
|
457
|
-
#
|
458
|
-
#
|
459
|
-
#
|
460
|
-
#
|
461
|
-
#
|
462
|
-
#
|
463
|
-
#
|
464
|
-
#
|
465
|
-
#
|
466
|
-
#
|
467
|
-
#
|
468
|
-
#
|
469
|
-
#
|
470
|
-
#
|
471
|
-
#
|
472
|
-
#
|
473
|
-
#
|
317
|
+
# 2017-07-02: [lb] removed this commented-out code from locallist body.
|
318
|
+
# See "Bundles" comments in TODO.taskpaper.
|
319
|
+
# This code builds the list of local items from all bundle
|
320
|
+
# subdirectories. Would that be how a bundles implementation
|
321
|
+
# works? Or would we rather just iterate over each bundle and
|
322
|
+
# process them separately, rather than all together at once?
|
323
|
+
#
|
324
|
+
# def locallist
|
325
|
+
# # so. if @locationbase/bundles exists
|
326
|
+
# # gather and merge: @locationbase/bundles/*/@location
|
327
|
+
# # then merge @locationbase/@location
|
328
|
+
# #
|
329
|
+
# bundleDir = $cfg['location.bundles'] or 'bundles'
|
330
|
+
# bundleDir = 'bundles' if bundleDir.nil?
|
331
|
+
# items = {}
|
332
|
+
# if (@locationbase + bundleDir).directory?
|
333
|
+
# (@locationbase + bundleDir).children.sort.each do |bndl|
|
334
|
+
# if (bndl + @location).exist?
|
335
|
+
# verbose("Loading from bundle #{bndl.basename}")
|
336
|
+
# bitems = localitems(bndl + @location)
|
337
|
+
# bitems.map!{|b| b[:bundled] = true; b} # mark items from bundles.
|
338
|
+
# # use synckey for quicker merging.
|
339
|
+
# bitems.each { |b| items[synckey(b)] = b }
|
340
|
+
# end
|
341
|
+
# end
|
474
342
|
# end
|
475
343
|
# end
|
476
|
-
# end
|
477
|
-
#end
|
478
344
|
#
|
479
345
|
def locallist(skip_warn: false)
|
480
346
|
items = {}
|
481
347
|
if location.exist?
|
482
348
|
# Get a list of SyncUpDown::Item's, or a class derived thereof.
|
483
349
|
bitems = localitems(location)
|
484
|
-
#
|
485
|
-
|
486
|
-
# 2017-07-02: If two files have the same identity, the simple loop
|
487
|
-
# masks that there are two files with the same identity. So check
|
488
|
-
# first for duplicates, and then process each item.
|
489
|
-
seen = {}
|
490
|
-
bitems.each do |item|
|
491
|
-
skey = synckey(item)
|
492
|
-
seen[skey] = seen.key?(skey) && seen[skey] + 1 || 1
|
493
|
-
end
|
350
|
+
# Check for duplicates first -- two files with the same identity.
|
351
|
+
seen = locallist_mark_seen(bitems)
|
494
352
|
counts = {}
|
495
353
|
bitems.each do |item|
|
496
|
-
|
497
|
-
if seen[skey] > 1
|
498
|
-
if items[skey].nil?
|
499
|
-
items[skey] = MrMurano::EventHandler::EventHandlerItem.new(item)
|
500
|
-
items[skey][:dup_count] = 0
|
501
|
-
end
|
502
|
-
counts[skey] = counts.key?(skey) && counts[skey] + 1 || 1
|
503
|
-
# Use a unique synckey so all duplicates make it in the list.
|
504
|
-
uniq_synckey = "#{skey}-#{counts[skey]}"
|
505
|
-
item[:dup_count] = counts[skey]
|
506
|
-
# This sets the alias for the output, so duplicates look unique.
|
507
|
-
item[@itemkey.to_sym] = uniq_synckey
|
508
|
-
items[uniq_synckey] = item
|
509
|
-
msg = "Duplicate definition found for #{fancy_ticks(skey)}"
|
510
|
-
if self.class.description.to_s != ''
|
511
|
-
msg += " for #{fancy_ticks(self.class.description)}"
|
512
|
-
end
|
513
|
-
warning(msg)
|
514
|
-
warning(" #{item.local_path}")
|
515
|
-
else
|
516
|
-
items[skey] = item
|
517
|
-
end
|
354
|
+
locallist_add_item(item, items, seen, counts)
|
518
355
|
end
|
519
356
|
elsif !skip_warn
|
520
|
-
|
521
|
-
unless @missing_complaints.include?(location)
|
522
|
-
# MEH/2017-07-31: This message is a little misleading on syncdown,
|
523
|
-
# e.g., in rspec ./spec/cmd_syncdown_spec.rb, one test blows away
|
524
|
-
# local directories and does a syncdown, and on stderr you'll see
|
525
|
-
# Skipping missing location
|
526
|
-
# ‘/tmp/d20170731-3150-1f50uj4/project/specs/resources.yaml’ (Resources)
|
527
|
-
# but then later in the syncdown, that directory and file gets created.
|
528
|
-
msg = "Skipping missing location #{fancy_ticks(location)}"
|
529
|
-
unless self.class.description.to_s.empty?
|
530
|
-
msg += " (#{Inflecto.pluralize(self.class.description)})"
|
531
|
-
end
|
532
|
-
warning(msg)
|
533
|
-
@missing_complaints << location
|
534
|
-
end
|
357
|
+
locallist_complain_missing
|
535
358
|
end
|
536
359
|
items.values
|
537
360
|
end
|
538
361
|
|
362
|
+
def locallist_mark_seen(bitems)
|
363
|
+
seen = {}
|
364
|
+
bitems.each do |item|
|
365
|
+
skey = synckey(item)
|
366
|
+
seen[skey] = seen.key?(skey) && seen[skey] + 1 || 1
|
367
|
+
end
|
368
|
+
seen
|
369
|
+
end
|
370
|
+
|
371
|
+
def locallist_add_item(item, items, seen, counts)
|
372
|
+
skey = synckey(item)
|
373
|
+
if seen[skey] > 1
|
374
|
+
if items[skey].nil?
|
375
|
+
items[skey] = item.clone
|
376
|
+
items[skey][:dup_count] = 0
|
377
|
+
end
|
378
|
+
counts[skey] = counts.key?(skey) && counts[skey] + 1 || 1
|
379
|
+
# Use a unique synckey so all duplicates make it in the list.
|
380
|
+
uniq_synckey = "#{skey}-#{counts[skey]}"
|
381
|
+
item[:dup_count] = counts[skey]
|
382
|
+
# This sets the alias for the output, so duplicates look unique.
|
383
|
+
item[@itemkey.to_sym] = uniq_synckey
|
384
|
+
items[uniq_synckey] = item
|
385
|
+
msg = "Duplicate definition found for #{fancy_ticks(skey)}"
|
386
|
+
if self.class.description.to_s != ''
|
387
|
+
msg += " for #{fancy_ticks(self.class.description)}"
|
388
|
+
end
|
389
|
+
warning(msg)
|
390
|
+
warning(" #{item.local_path}")
|
391
|
+
else
|
392
|
+
items[skey] = item
|
393
|
+
end
|
394
|
+
end
|
395
|
+
|
396
|
+
def locallist_complain_missing
|
397
|
+
@missing_complaints = [] unless defined?(@missing_complaints)
|
398
|
+
return if @missing_complaints.include?(location)
|
399
|
+
# MEH/2017-07-31: This message is a little misleading on syncdown,
|
400
|
+
# e.g., in rspec ./spec/cmd_syncdown_spec.rb, one test blows away
|
401
|
+
# local directories and does a syncdown, and on stderr you'll see
|
402
|
+
# Skipping missing location
|
403
|
+
# ‘/tmp/d20170731-3150-1f50uj4/project/specs/resources.yaml’ (Resources)
|
404
|
+
# but then later in the syncdown, that directory and file gets created.
|
405
|
+
msg = "Skipping missing location #{fancy_ticks(location)}"
|
406
|
+
unless self.class.description.to_s.empty?
|
407
|
+
msg += " (#{Inflecto.pluralize(self.class.description)})"
|
408
|
+
end
|
409
|
+
warning(msg)
|
410
|
+
@missing_complaints << location
|
411
|
+
end
|
412
|
+
|
413
|
+
# Some items are considered "undeletable", meaning if a corresponding
|
414
|
+
# file does not exist locally, or if the user deletes such a file, we
|
415
|
+
# do not delete it on the server, but instead set it to the empty string.
|
416
|
+
# The reverse is also true: if a service script on the platform is empty,
|
417
|
+
# we do not need to create a file for it locally.
|
539
418
|
def resurrect_undeletables(localbox, _therebox)
|
540
419
|
# It's up to the Syncables to implement this, if they care.
|
541
420
|
localbox
|
@@ -589,7 +468,7 @@ module MrMurano
|
|
589
468
|
if ::File.directory?(path)
|
590
469
|
true
|
591
470
|
else
|
592
|
-
ignoring.any? { |pattern|
|
471
|
+
ignoring.any? { |pattern| ignore?(path, pattern) }
|
593
472
|
end
|
594
473
|
end
|
595
474
|
items = items.map do |path|
|
@@ -604,7 +483,10 @@ module MrMurano
|
|
604
483
|
end
|
605
484
|
item = to_remote_item(from, rpath)
|
606
485
|
if item.is_a?(Array)
|
607
|
-
item.compact.map
|
486
|
+
item.compact.map do |itm|
|
487
|
+
itm[:local_path] = rpath
|
488
|
+
itm
|
489
|
+
end
|
608
490
|
elsif !item.nil?
|
609
491
|
item[:local_path] = rpath
|
610
492
|
item
|
@@ -649,456 +531,6 @@ module MrMurano
|
|
649
531
|
def config_vars_encode(script)
|
650
532
|
script
|
651
533
|
end
|
652
|
-
|
653
|
-
#######################################################################
|
654
|
-
# Methods that provide the core status/syncup/syncdown
|
655
|
-
|
656
|
-
def sync_update_progress(msg)
|
657
|
-
if $cfg['tool.no-progress']
|
658
|
-
say(msg)
|
659
|
-
else
|
660
|
-
MrMurano::Verbose.verbose(msg + "\n")
|
661
|
-
end
|
662
|
-
end
|
663
|
-
|
664
|
-
## Make things in Murano look like local project
|
665
|
-
#
|
666
|
-
# This creates, uploads, and deletes things as needed up in Murano to match
|
667
|
-
# what is in the local project directory.
|
668
|
-
#
|
669
|
-
# @param options [Hash, Commander::Command::Options] Options on operation
|
670
|
-
# @param selected [Array<String>] Filters for _matcher
|
671
|
-
def syncup(options={}, selected=[])
|
672
|
-
return 0 unless api_id?
|
673
|
-
|
674
|
-
options = elevate_hash(options)
|
675
|
-
options[:asdown] = false
|
676
|
-
|
677
|
-
num_synced = 0
|
678
|
-
|
679
|
-
syncup_before
|
680
|
-
|
681
|
-
dt = status(options, selected)
|
682
|
-
|
683
|
-
toadd = dt[:toadd]
|
684
|
-
todel = dt[:todel]
|
685
|
-
tomod = dt[:tomod]
|
686
|
-
|
687
|
-
itemkey = @itemkey.to_sym
|
688
|
-
todel.each do |item|
|
689
|
-
syncup_item(item, options, :delete, 'Removing') do |aitem|
|
690
|
-
remove(aitem[itemkey])
|
691
|
-
num_synced += 1
|
692
|
-
end
|
693
|
-
end
|
694
|
-
toadd.each do |item|
|
695
|
-
syncup_item(item, options, :create, 'Adding') do |aitem|
|
696
|
-
upload(aitem[:local_path], aitem.reject { |k, _v| k == :local_path }, false)
|
697
|
-
num_synced += 1
|
698
|
-
end
|
699
|
-
end
|
700
|
-
tomod.each do |item|
|
701
|
-
syncup_item(item, options, :update, 'Updating') do |aitem|
|
702
|
-
upload(aitem[:local_path], aitem.reject { |k, _v| k == :local_path }, true)
|
703
|
-
num_synced += 1
|
704
|
-
end
|
705
|
-
end
|
706
|
-
|
707
|
-
syncup_after
|
708
|
-
|
709
|
-
MrMurano::Verbose.whirly_stop(force: true)
|
710
|
-
|
711
|
-
num_synced
|
712
|
-
end
|
713
|
-
|
714
|
-
def syncup_item(item, options, action, verbage)
|
715
|
-
if options[action]
|
716
|
-
# It's up to the callback to check and honor $cfg['tool.dry'].
|
717
|
-
prog_msg = "#{verbage.capitalize} item #{item[:synckey]}"
|
718
|
-
prog_msg += " (#{item[:synctype]})" if $cfg['tool.verbose']
|
719
|
-
sync_update_progress(prog_msg)
|
720
|
-
yield item
|
721
|
-
elsif $cfg['tool.verbose']
|
722
|
-
MrMurano::Verbose.whirly_interject do
|
723
|
-
say("--no-#{action}: Not #{verbage.downcase} item #{item[:synckey]}")
|
724
|
-
end
|
725
|
-
end
|
726
|
-
end
|
727
|
-
|
728
|
-
## Make things in local project look like Murano
|
729
|
-
#
|
730
|
-
# This creates, downloads, and deletes things as needed up in the local project
|
731
|
-
# directory to match what is in Murano.
|
732
|
-
#
|
733
|
-
# @param options [Hash, Commander::Command::Options] Options on operation
|
734
|
-
# @param selected [Array<String>] Filters for _matcher
|
735
|
-
def syncdown(options={}, selected=[])
|
736
|
-
return 0 unless api_id?
|
737
|
-
|
738
|
-
options = elevate_hash(options)
|
739
|
-
options[:asdown] = true
|
740
|
-
options[:skip_missing_warning] = true
|
741
|
-
|
742
|
-
num_synced = 0
|
743
|
-
|
744
|
-
syncdown_before
|
745
|
-
|
746
|
-
dt = status(options, selected)
|
747
|
-
|
748
|
-
toadd = dt[:toadd]
|
749
|
-
todel = dt[:todel]
|
750
|
-
tomod = dt[:tomod]
|
751
|
-
|
752
|
-
into = location
|
753
|
-
todel.each do |item|
|
754
|
-
syncdown_item(item, into, options, :delete, 'Removing') do |dest, aitem|
|
755
|
-
removelocal(dest, aitem)
|
756
|
-
num_synced += 1
|
757
|
-
end
|
758
|
-
end
|
759
|
-
toadd.each do |item|
|
760
|
-
syncdown_item(item, into, options, :create, 'Adding') do |dest, aitem|
|
761
|
-
download(dest, aitem, options)
|
762
|
-
num_synced += 1
|
763
|
-
end
|
764
|
-
end
|
765
|
-
tomod.each do |item|
|
766
|
-
syncdown_item(item, into, options, :update, 'Updating') do |dest, aitem|
|
767
|
-
download(dest, aitem, options)
|
768
|
-
num_synced += 1
|
769
|
-
end
|
770
|
-
end
|
771
|
-
syncdown_after(into)
|
772
|
-
|
773
|
-
num_synced
|
774
|
-
end
|
775
|
-
|
776
|
-
def syncdown_item(item, into, options, action, verbage)
|
777
|
-
if options[action]
|
778
|
-
prog_msg = "#{verbage.capitalize} item #{item[:synckey]}"
|
779
|
-
prog_msg += " (#{item[:synctype]})" if $cfg['tool.verbose']
|
780
|
-
sync_update_progress(prog_msg)
|
781
|
-
dest = tolocalpath(into, item)
|
782
|
-
yield dest, item
|
783
|
-
elsif $cfg['tool.verbose']
|
784
|
-
say("--no-#{action}: Not #{verbage.downcase} item #{item[:synckey]}")
|
785
|
-
end
|
786
|
-
end
|
787
|
-
|
788
|
-
## Call external diff tool on item
|
789
|
-
#
|
790
|
-
# WARNING: This will download the remote item to do the diff.
|
791
|
-
#
|
792
|
-
# @param merged [merged] The merged item to get a diff of
|
793
|
-
# @local local, unadulterated (non-merged) item
|
794
|
-
# @return [String] The diff output
|
795
|
-
def dodiff(merged, local, _there=nil, asdown=false)
|
796
|
-
trmt = Tempfile.new([tolocalname(merged, @itemkey) + '_remote_', '.lua'])
|
797
|
-
tlcl = Tempfile.new([tolocalname(merged, @itemkey) + '_local_', '.lua'])
|
798
|
-
Pathname.new(tlcl.path).open('wb') do |io|
|
799
|
-
if merged.key?(:script)
|
800
|
-
io << config_vars_decode(merged[:script])
|
801
|
-
else
|
802
|
-
# For most items, read the local file.
|
803
|
-
# For resources, it's a bit trickier.
|
804
|
-
# NOTE: This class adds a :selected key to the local item that we need
|
805
|
-
# to remove, since it's not part of the remote items that gets downloaded.
|
806
|
-
local = local.reject { |k, _v| k == :selected } unless local.nil?
|
807
|
-
diff_item_write(io, merged, local, nil)
|
808
|
-
end
|
809
|
-
end
|
810
|
-
stdout_and_stderr = ''
|
811
|
-
begin
|
812
|
-
tmp_path = Pathname.new(trmt.path)
|
813
|
-
diff_download(tmp_path, merged)
|
814
|
-
|
815
|
-
MrMurano::Verbose.whirly_stop
|
816
|
-
|
817
|
-
# 2017-07-03: No worries, Ruby 3.0 frozen string literals, cmd is a list.
|
818
|
-
cmd = $cfg['diff.cmd'].shellsplit
|
819
|
-
# ALT_SEPARATOR is the platform specific alternative separator,
|
820
|
-
# for Windows support.
|
821
|
-
remote_path = trmt.path.gsub(
|
822
|
-
::File::SEPARATOR, ::File::ALT_SEPARATOR || ::File::SEPARATOR
|
823
|
-
)
|
824
|
-
local_path = tlcl.path.gsub(
|
825
|
-
::File::SEPARATOR, ::File::ALT_SEPARATOR || ::File::SEPARATOR
|
826
|
-
)
|
827
|
-
if asdown
|
828
|
-
cmd << local_path
|
829
|
-
cmd << remote_path
|
830
|
-
else
|
831
|
-
cmd << remote_path
|
832
|
-
cmd << local_path
|
833
|
-
end
|
834
|
-
|
835
|
-
stdout_and_stderr, _status = Open3.capture2e(*cmd)
|
836
|
-
# How important are the first two lines of the diff? E.g.,
|
837
|
-
# --- /tmp/raw_data_remote_20170718-20183-gdyeg9.lua 2017-07-18 13:13:13.864051905 -0500
|
838
|
-
# +++ /tmp/raw_data_local_20170718-20183-71o4me.lua 2017-07-18 13:13:14.520049397 -0500
|
839
|
-
# Seems like printing the path to a since-deleted temporary file is misleading.
|
840
|
-
if $cfg['diff.cmd'] == 'diff' || $cfg['diff.cmd'].start_with?('diff ')
|
841
|
-
lineno = 0
|
842
|
-
consise = stdout_and_stderr.lines.reject do |line|
|
843
|
-
lineno += 1
|
844
|
-
if lineno == 1 && line.start_with?('--- ')
|
845
|
-
true
|
846
|
-
elsif lineno == 2 && line.start_with?('+++ ')
|
847
|
-
true
|
848
|
-
else
|
849
|
-
false
|
850
|
-
end
|
851
|
-
end
|
852
|
-
stdout_and_stderr = consise.join
|
853
|
-
end
|
854
|
-
ensure
|
855
|
-
trmt.close
|
856
|
-
trmt.unlink
|
857
|
-
tlcl.close
|
858
|
-
tlcl.unlink
|
859
|
-
end
|
860
|
-
stdout_and_stderr
|
861
|
-
end
|
862
|
-
|
863
|
-
##
|
864
|
-
# Check if an item matches a pattern.
|
865
|
-
# @param items [Array<Item>] Of items to filter
|
866
|
-
# @param patterns [Array<String>] Filters for _matcher
|
867
|
-
def _matcher(items, patterns)
|
868
|
-
items.map do |item|
|
869
|
-
if patterns.empty?
|
870
|
-
item[:selected] = true
|
871
|
-
else
|
872
|
-
item[:selected] = patterns.any? do |pattern|
|
873
|
-
if pattern.to_s[0] == '#'
|
874
|
-
match(item, pattern)
|
875
|
-
elsif !defined?(item.local_path) || item.local_path.nil?
|
876
|
-
false
|
877
|
-
else
|
878
|
-
item[:local_path].fnmatch(pattern)
|
879
|
-
end
|
880
|
-
end
|
881
|
-
end
|
882
|
-
item
|
883
|
-
end
|
884
|
-
end
|
885
|
-
private :_matcher
|
886
|
-
|
887
|
-
## Get status of things here verses there
|
888
|
-
#
|
889
|
-
# @param options [Hash, Commander::Command::Options] Options on operation
|
890
|
-
# @param selected [Array<String>] Filters for _matcher
|
891
|
-
# @return [Hash{Symbol=>Array<Item>}] Items grouped by the action that should be taken
|
892
|
-
def status(options={}, selected=[])
|
893
|
-
options = elevate_hash(options)
|
894
|
-
|
895
|
-
ret = filter_solution(options)
|
896
|
-
return ret unless ret.nil?
|
897
|
-
|
898
|
-
therebox, localbox = items_lists(options, selected)
|
899
|
-
|
900
|
-
toadd, todel = items_new_and_old(options, therebox, localbox)
|
901
|
-
|
902
|
-
tomod, unchg = items_mods_and_chgs(options, therebox, localbox)
|
903
|
-
|
904
|
-
clash = items_cull_clashes!([toadd, todel, tomod, unchg])
|
905
|
-
|
906
|
-
if options[:unselected]
|
907
|
-
{
|
908
|
-
toadd: toadd,
|
909
|
-
todel: todel,
|
910
|
-
tomod: tomod,
|
911
|
-
unchg: unchg,
|
912
|
-
skipd: [],
|
913
|
-
clash: clash,
|
914
|
-
}
|
915
|
-
else
|
916
|
-
{
|
917
|
-
toadd: select_selected(toadd),
|
918
|
-
todel: select_selected(todel),
|
919
|
-
tomod: select_selected(tomod),
|
920
|
-
unchg: select_selected(unchg),
|
921
|
-
skipd: [],
|
922
|
-
clash: select_selected(clash),
|
923
|
-
}
|
924
|
-
end
|
925
|
-
end
|
926
|
-
|
927
|
-
def filter_solution(options)
|
928
|
-
# Get the solution name from the config.
|
929
|
-
# Convert, e.g., application.id => application.name
|
930
|
-
soln_name = $cfg[@solntype.gsub(/(.*)\.id/, '\1.name')]
|
931
|
-
# Skip this syncable if the api_id is not set, or if user wants to skip
|
932
|
-
# by solution.
|
933
|
-
skip_sol = false
|
934
|
-
if !api_id? ||
|
935
|
-
(options[:type] == :application && @solntype != 'application.id') ||
|
936
|
-
(options[:type] == :product && @solntype != 'product.id')
|
937
|
-
skip_sol = true
|
938
|
-
else
|
939
|
-
tested = false
|
940
|
-
passed = false
|
941
|
-
if @solntype == 'application.id'
|
942
|
-
# elevate_hash makes the hash return false rather than
|
943
|
-
# nil on unknown keys, so preface with a key? guard.
|
944
|
-
if options.key?(:application) && !options[:application].to_s.empty?
|
945
|
-
if soln_name =~ /#{Regexp.escape(options[:application])}/i ||
|
946
|
-
api_id =~ /#{Regexp.escape(options[:application])}/i
|
947
|
-
passed = true
|
948
|
-
end
|
949
|
-
tested = true
|
950
|
-
end
|
951
|
-
if options.key?(:application_id) && !options[:application_id].to_s.empty?
|
952
|
-
passed = true if options[:application_id] == api_id
|
953
|
-
tested = true
|
954
|
-
end
|
955
|
-
if options.key?(:application_name) && !options[:application_name].to_s.empty?
|
956
|
-
passed = true if options[:application_name] == soln_name
|
957
|
-
tested = true
|
958
|
-
end
|
959
|
-
elsif @solntype == 'product.id'
|
960
|
-
if options.key?(:product) && !options[:product].to_s.empty?
|
961
|
-
if soln_name =~ /#{Regexp.escape(options[:product])}/i ||
|
962
|
-
api_id =~ /#{Regexp.escape(options[:product])}/i
|
963
|
-
passed = true
|
964
|
-
end
|
965
|
-
tested = true
|
966
|
-
end
|
967
|
-
if options.key?(:product_id) && !options[:product_id].to_s.empty?
|
968
|
-
passed = true if options[:product_id] == api_id
|
969
|
-
tested = true
|
970
|
-
end
|
971
|
-
if options.key?(:product_name) && !options[:product_name].to_s.empty?
|
972
|
-
passed = true if options[:product_name] == soln_name
|
973
|
-
tested = true
|
974
|
-
end
|
975
|
-
end
|
976
|
-
skip_sol = true if tested && !passed
|
977
|
-
end
|
978
|
-
return nil unless skip_sol
|
979
|
-
ret = { toadd: [], todel: [], tomod: [], unchg: [], skipd: [], clash: [] }
|
980
|
-
ret[:skipd] << { synckey: self.class.description }
|
981
|
-
ret
|
982
|
-
end
|
983
|
-
|
984
|
-
def syncable_validate_api_id
|
985
|
-
# 2017-07-02: Now that there are multiple solution types, and because
|
986
|
-
# SyncRoot.add is called on different classes that go with either or
|
987
|
-
# both products and applications, if a user only created one solution,
|
988
|
-
# then some syncables will have their api_id set to -1, because there's
|
989
|
-
# not a corresponding solution in Murano.
|
990
|
-
raise 'Syncable missing api_id or not valid_api_id??!' unless api_id?
|
991
|
-
end
|
992
|
-
|
993
|
-
def items_lists(options, selected)
|
994
|
-
# Fetch arrays of items there, and items here/local.
|
995
|
-
there = list
|
996
|
-
local = locallist(skip_warn: options[:skip_missing_warning])
|
997
|
-
|
998
|
-
resolve_config_var_usage!(there, local)
|
999
|
-
|
1000
|
-
there = _matcher(there, selected)
|
1001
|
-
local = _matcher(local, selected)
|
1002
|
-
|
1003
|
-
therebox = {}
|
1004
|
-
there.each do |item|
|
1005
|
-
item[:synckey] = synckey(item)
|
1006
|
-
item[:synctype] = self.class.description
|
1007
|
-
therebox[item[:synckey]] = item
|
1008
|
-
end
|
1009
|
-
|
1010
|
-
localbox = {}
|
1011
|
-
local.each do |item|
|
1012
|
-
skey = synckey(item)
|
1013
|
-
# 2017-07-02: Check for local duplicates.
|
1014
|
-
unless item[:dup_count].nil? || item[:dup_count].zero?
|
1015
|
-
skey += "-#{item[:dup_count]}"
|
1016
|
-
end
|
1017
|
-
item[:synckey] = skey
|
1018
|
-
item[:synctype] = self.class.description
|
1019
|
-
localbox[skey] = item
|
1020
|
-
end
|
1021
|
-
|
1022
|
-
# Some items are considered "undeletable", meaning if a
|
1023
|
-
# corresponding file does not exist locally, we assume
|
1024
|
-
# it does but is just set to the empty string.
|
1025
|
-
localbox = resurrect_undeletables(localbox, therebox)
|
1026
|
-
|
1027
|
-
[therebox, localbox]
|
1028
|
-
end
|
1029
|
-
|
1030
|
-
def items_new_and_old(options, therebox, localbox)
|
1031
|
-
if options[:asdown]
|
1032
|
-
todel = (localbox.keys - therebox.keys).map { |key| localbox[key] }
|
1033
|
-
toadd = (therebox.keys - localbox.keys).map { |key| therebox[key] }
|
1034
|
-
else
|
1035
|
-
toadd = (localbox.keys - therebox.keys).map { |key| localbox[key] }
|
1036
|
-
todel = (therebox.keys - localbox.keys).map { |key| therebox[key] }
|
1037
|
-
end
|
1038
|
-
[sort_by_name(toadd), sort_by_name(todel)]
|
1039
|
-
end
|
1040
|
-
|
1041
|
-
def items_mods_and_chgs(options, therebox, localbox)
|
1042
|
-
tomod = []
|
1043
|
-
unchg = []
|
1044
|
-
|
1045
|
-
(localbox.keys & therebox.keys).each do |key|
|
1046
|
-
# Skip this item if it's got duplicate conflicts.
|
1047
|
-
next if !localbox[key].is_a?(Hash) && localbox[key].dup_count == 0
|
1048
|
-
# Want 'local' to override 'there' except for itemkey.
|
1049
|
-
if options[:asdown]
|
1050
|
-
mrg = therebox[key].reject { |k, _v| k == @itemkey.to_sym }
|
1051
|
-
mrg = localbox[key].merge(mrg)
|
1052
|
-
else
|
1053
|
-
mrg = localbox[key].reject { |k, _v| k == @itemkey.to_sym }
|
1054
|
-
mrg = therebox[key].merge(mrg)
|
1055
|
-
end
|
1056
|
-
|
1057
|
-
if docmp(localbox[key], therebox[key])
|
1058
|
-
if options[:diff] && mrg[:selected]
|
1059
|
-
mrg[:diff] = dodiff(mrg.to_h, localbox[key], therebox[key], options[:asdown])
|
1060
|
-
end
|
1061
|
-
tomod << mrg
|
1062
|
-
else
|
1063
|
-
unchg << mrg
|
1064
|
-
end
|
1065
|
-
end
|
1066
|
-
[sort_by_name(tomod), sort_by_name(unchg)]
|
1067
|
-
end
|
1068
|
-
|
1069
|
-
def sort_by_name(list)
|
1070
|
-
if list.any? && list.first.is_a?(Hash)
|
1071
|
-
# AFAIK, only SyncUpDown_spec.rb comes through here, because
|
1072
|
-
# it does not use SyncUpDown::Item but mocks its own items
|
1073
|
-
# using hashes (see calls to and_return). [lb]
|
1074
|
-
list.sort_by { |hsh| hsh[:name] }
|
1075
|
-
else
|
1076
|
-
list.sort_by(&:name)
|
1077
|
-
end
|
1078
|
-
end
|
1079
|
-
|
1080
|
-
def select_selected(items)
|
1081
|
-
items.select { |i| i[:selected] }.map { |i| i.delete(:selected); i }
|
1082
|
-
end
|
1083
|
-
|
1084
|
-
def items_cull_clashes!(items_list)
|
1085
|
-
items_list = [items_list] unless items_list.is_a?(Array)
|
1086
|
-
clash = []
|
1087
|
-
items_list.each do |items|
|
1088
|
-
items.select! do |item|
|
1089
|
-
if item[:dup_count].nil?
|
1090
|
-
true
|
1091
|
-
elsif item[:dup_count].zero?
|
1092
|
-
# This is the control item.
|
1093
|
-
false
|
1094
|
-
else
|
1095
|
-
clash.push(item)
|
1096
|
-
false
|
1097
|
-
end
|
1098
|
-
end
|
1099
|
-
end
|
1100
|
-
clash
|
1101
|
-
end
|
1102
534
|
end
|
1103
535
|
end
|
1104
536
|
|