MuranoCLI 3.2.0.beta.1 → 3.2.0.beta.5

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.
Files changed (111) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +4 -1
  3. data/.trustme.plugin +137 -0
  4. data/.trustme.sh +217 -117
  5. data/.trustme.vim +9 -3
  6. data/Gemfile +9 -3
  7. data/MuranoCLI.gemspec +8 -5
  8. data/Rakefile +1 -0
  9. data/dockers/Dockerfile.2.2.9 +6 -3
  10. data/dockers/Dockerfile.2.3.6 +6 -3
  11. data/dockers/Dockerfile.2.4.3 +6 -3
  12. data/dockers/Dockerfile.2.5.0 +6 -3
  13. data/dockers/Dockerfile.GemRelease +10 -8
  14. data/dockers/Dockerfile.m4 +23 -5
  15. data/dockers/docker-test.sh +65 -28
  16. data/docs/completions/murano_completion-bash +751 -57
  17. data/docs/develop.rst +10 -9
  18. data/lib/MrMurano/AccountBase.rb +95 -6
  19. data/lib/MrMurano/Commander-Entry.rb +9 -4
  20. data/lib/MrMurano/Config-Migrate.rb +2 -0
  21. data/lib/MrMurano/Config.rb +94 -26
  22. data/lib/MrMurano/Content.rb +1 -1
  23. data/lib/MrMurano/Exchange.rb +77 -42
  24. data/lib/MrMurano/Gateway.rb +1 -1
  25. data/lib/MrMurano/HttpAuthed.rb +20 -7
  26. data/lib/MrMurano/Logs.rb +10 -1
  27. data/lib/MrMurano/ProjectFile.rb +1 -1
  28. data/lib/MrMurano/ReCommander.rb +129 -73
  29. data/lib/MrMurano/Solution-ServiceConfig.rb +18 -11
  30. data/lib/MrMurano/Solution-Services.rb +78 -50
  31. data/lib/MrMurano/Solution-Users.rb +1 -1
  32. data/lib/MrMurano/Solution.rb +13 -63
  33. data/lib/MrMurano/SyncUpDown-Core.rb +185 -77
  34. data/lib/MrMurano/SyncUpDown-Item.rb +29 -4
  35. data/lib/MrMurano/SyncUpDown.rb +11 -11
  36. data/lib/MrMurano/Webservice-Cors.rb +1 -1
  37. data/lib/MrMurano/Webservice-Endpoint.rb +28 -17
  38. data/lib/MrMurano/Webservice-File.rb +103 -43
  39. data/lib/MrMurano/commands/domain.rb +1 -0
  40. data/lib/MrMurano/commands/element.rb +585 -0
  41. data/lib/MrMurano/commands/exchange.rb +211 -204
  42. data/lib/MrMurano/commands/gb.rb +1 -0
  43. data/lib/MrMurano/commands/globals.rb +17 -7
  44. data/lib/MrMurano/commands/init.rb +115 -101
  45. data/lib/MrMurano/commands/keystore.rb +1 -1
  46. data/lib/MrMurano/commands/logs.rb +2 -1
  47. data/lib/MrMurano/commands/postgresql.rb +17 -7
  48. data/lib/MrMurano/commands/service.rb +572 -0
  49. data/lib/MrMurano/commands/show.rb +7 -3
  50. data/lib/MrMurano/commands/solution.rb +2 -1
  51. data/lib/MrMurano/commands/solution_picker.rb +31 -15
  52. data/lib/MrMurano/commands/status.rb +205 -169
  53. data/lib/MrMurano/commands/sync.rb +70 -38
  54. data/lib/MrMurano/commands/token.rb +59 -14
  55. data/lib/MrMurano/commands/usage.rb +1 -0
  56. data/lib/MrMurano/commands.rb +2 -0
  57. data/lib/MrMurano/hash.rb +91 -0
  58. data/lib/MrMurano/http.rb +55 -6
  59. data/lib/MrMurano/makePretty.rb +47 -0
  60. data/lib/MrMurano/optparse.rb +60 -45
  61. data/lib/MrMurano/variegated/TruthyFalsey.rb +48 -0
  62. data/lib/MrMurano/variegated/ruby_dig.rb +64 -0
  63. data/lib/MrMurano/verbosing.rb +113 -3
  64. data/lib/MrMurano/version.rb +1 -1
  65. data/spec/Account_spec.rb +34 -20
  66. data/spec/Business_spec.rb +12 -9
  67. data/spec/Config_spec.rb +7 -1
  68. data/spec/Content_spec.rb +17 -1
  69. data/spec/GatewayBase_spec.rb +5 -2
  70. data/spec/GatewayDevice_spec.rb +4 -2
  71. data/spec/GatewayResource_spec.rb +4 -1
  72. data/spec/GatewaySettings_spec.rb +4 -1
  73. data/spec/HttpAuthed_spec.rb +73 -0
  74. data/spec/Http_spec.rb +32 -35
  75. data/spec/ProjectFile_spec.rb +1 -1
  76. data/spec/Solution-ServiceConfig_spec.rb +4 -1
  77. data/spec/Solution-ServiceEventHandler_spec.rb +6 -3
  78. data/spec/Solution-ServiceModules_spec.rb +4 -1
  79. data/spec/Solution-UsersRoles_spec.rb +4 -1
  80. data/spec/Solution_spec.rb +4 -1
  81. data/spec/SyncUpDown_spec.rb +1 -1
  82. data/spec/Webservice-Cors_spec.rb +4 -1
  83. data/spec/Webservice-Endpoint_spec.rb +9 -6
  84. data/spec/Webservice-File_spec.rb +17 -4
  85. data/spec/Webservice-Setting_spec.rb +6 -2
  86. data/spec/_workspace.rb +2 -0
  87. data/spec/cmd_common.rb +42 -13
  88. data/spec/cmd_content_spec.rb +17 -7
  89. data/spec/cmd_device_spec.rb +1 -1
  90. data/spec/cmd_domain_spec.rb +2 -2
  91. data/spec/cmd_element_spec.rb +400 -0
  92. data/spec/cmd_exchange_spec.rb +2 -2
  93. data/spec/cmd_init_spec.rb +59 -25
  94. data/spec/cmd_keystore_spec.rb +6 -3
  95. data/spec/cmd_link_spec.rb +10 -5
  96. data/spec/cmd_logs_spec.rb +1 -1
  97. data/spec/cmd_setting_application_spec.rb +18 -15
  98. data/spec/cmd_setting_product_spec.rb +7 -7
  99. data/spec/cmd_status_spec.rb +27 -17
  100. data/spec/cmd_syncdown_application_spec.rb +30 -3
  101. data/spec/cmd_syncdown_both_spec.rb +72 -18
  102. data/spec/cmd_syncup_spec.rb +71 -5
  103. data/spec/cmd_token_spec.rb +2 -2
  104. data/spec/cmd_usage_spec.rb +2 -2
  105. data/spec/dry_run_formatter.rb +27 -0
  106. data/spec/fixtures/dumped_config +8 -0
  107. data/spec/fixtures/exchange_element/element-show.json +1 -0
  108. data/spec/fixtures/exchange_element/swagger-mur-6407__10k.yaml +282 -0
  109. data/spec/fixtures/exchange_element/swagger-mur-6407__20k.yaml +588 -0
  110. data/spec/variegated_TruthyFalsey_spec.rb +29 -0
  111. metadata +51 -25
@@ -6,6 +6,7 @@
6
6
  # Unauthorized copying of this file is strictly prohibited.
7
7
 
8
8
  require 'MrMurano/AccountBase'
9
+ require 'MrMurano/Business'
9
10
  require 'MrMurano/Exchange-Element'
10
11
  require 'MrMurano/verbosing'
11
12
 
@@ -19,56 +20,47 @@ module MrMurano
19
20
  super
20
21
  end
21
22
 
23
+ def put(element_id, body={}, &block)
24
+ super('exchange/' + bid + '/element/' + element_id, body, &block)
25
+ end
26
+
22
27
  def element(element_id)
28
+ element_by_id(element_id)
29
+ end
30
+
31
+ def element_by_id(element_id)
23
32
  ret = get('exchange/' + bid + '/element/' + element_id)
24
33
  return nil unless ret.is_a?(Hash) && !ret.key?(:error)
25
34
  ret
26
35
  end
27
36
 
28
- def fetch_type(part)
29
- # [lb] not super happy about mixing presentation with other logic
30
- # but this is quick and dirty.
31
- case part
32
- when '/element/'
33
- qualifier = 'All'
34
- when '/purchase/'
35
- qualifier = 'Purchased'
36
- else
37
- raise 'Unexpected error'
38
- end
39
- whirly_start("Fetching #{qualifier} Elements...")
37
+ def element_quick(element_id)
38
+ # Check if ID, a 24-character hexadecimal string.
39
+ smells_like_id = element_id =~ /^[\h]{24}$/
40
+ return element_by_id(element_id) if smells_like_id
41
+ nil
42
+ end
40
43
 
41
- # FIXME/2017-09-27: Support Pagination. BizAPI accepts four settings:
42
- # type, offset, limit, and select.
43
- # <bizapi>/lib/api/route/exchange/schemas/element.js
44
- ret = get('exchange/' + bid + part) do |request, http|
45
- response = http.request(request)
46
- case response
47
- when Net::HTTPSuccess
48
- workit_response(response)
49
- else
50
- showHttpError(request, response)
51
- end
52
- end
53
- whirly_stop
54
- return [] unless ret.is_a?(Hash) && !ret.key?(:error)
55
- return [] unless ret.key?(:items)
56
- unless ret[:count] == ret[:items].length
57
- warning(
58
- 'Unexpected: ret[:count] != ret[:items].length: ' \
59
- "#{ret[:count]} != #{ret[:items].length}"
60
- )
44
+ def elements(skip_purchased: false, **opts)
45
+ # Get the user's Business metadata, including their Business tier.
46
+ overview if @ometa.nil?
47
+
48
+ elems = nil
49
+ id_maybe = opts[:filter_id] || opts[:filter_fuzzy]
50
+ if id_maybe && skip_purchased
51
+ elem = element_quick(id_maybe)
52
+ elems = [MrMurano::ExchangeElement.new(elem)] unless elem.nil?
61
53
  end
62
- ret[:items]
54
+ elems = elements_all(skip_purchased: skip_purchased) if elems.nil?
55
+
56
+ prepare_elements(elems, **opts)
63
57
  end
64
58
 
65
- def elements(**opts)
59
+ def elements_all(skip_purchased: false)
66
60
  lookp = {}
67
- # Get the user's Business metadata, including their Business tier.
68
- overview if @ometa.nil?
69
61
  elems = fetch_elements(lookp)
70
- fetch_purchased(lookp)
71
- prepare_elements(elems, **opts)
62
+ fetch_purchased(lookp) unless skip_purchased
63
+ elems
72
64
  end
73
65
 
74
66
  def fetch_elements(lookp)
@@ -82,9 +74,11 @@ module MrMurano
82
74
  end
83
75
  end
84
76
 
85
- def fetch_purchased(lookp)
77
+ def fetch_purchased(lookp=nil, services_only: false)
78
+ query = [%w[type service]] if services_only
86
79
  # Fetch the list of Purchased elements.
87
- items = fetch_type('/purchase/')
80
+ items = fetch_type('/purchase/', query)
81
+ return items if lookp.nil?
88
82
  # Update the list of all Elements to indicate which have been purchased.
89
83
  items.each do |meta|
90
84
  elem = lookp[meta[:elementId]]
@@ -99,15 +93,56 @@ module MrMurano
99
93
  )
100
94
  end
101
95
  else
102
- warning("Unexpected: No Element found for Exchange Purchase: elementId: #{meta[:elementId]}")
96
+ warning(
97
+ "No Exchange Element for Purchase: elementId: #{meta[:elementId]}"
98
+ )
99
+ end
100
+ end
101
+ end
102
+
103
+ def fetch_type(part, query=nil)
104
+ # [lb] not super happy about mixing presentation with other logic
105
+ # but this is quick and dirty.
106
+ case part
107
+ when '/element/'
108
+ qualifier = 'All'
109
+ when '/purchase/'
110
+ qualifier = 'Purchased'
111
+ else
112
+ raise 'Unexpected error'
113
+ end
114
+ whirly_start("Fetching #{qualifier} Elements...")
115
+
116
+ # FIXME/2017-09-27: Support Pagination. BizAPI accepts four settings:
117
+ # type, offset, limit, and select.
118
+ # <bizapi>/lib/api/route/exchange/schemas/element.js
119
+ ret = get('exchange/' + bid + part, query) do |request, http|
120
+ response = http.request(request)
121
+ case response
122
+ when Net::HTTPSuccess
123
+ workit_response(response)
124
+ else
125
+ showHttpError(request, response)
103
126
  end
104
127
  end
128
+ whirly_stop
129
+ return [] unless ret.is_a?(Hash) && !ret.key?(:error)
130
+ return [] unless ret.key?(:items)
131
+ unless ret[:count] == ret[:items].length
132
+ warning(
133
+ 'Unexpected: ret[:count] != ret[:items].length: ' \
134
+ "#{ret[:count]} != #{ret[:items].length}"
135
+ )
136
+ end
137
+ ret[:items]
105
138
  end
106
139
 
107
140
  def verify_purchase_vs_element(elem, key, val)
108
141
  elem.send(key) == val
109
142
  rescue NoMethodError
110
- verbose("Unexpected: Exchange Element missing key found in Purchase Element: #{key}")
143
+ verbose(
144
+ "Unexpected: Exchange Element missing key found in Purchase Element: #{key}"
145
+ )
111
146
  true
112
147
  end
113
148
 
@@ -386,7 +386,7 @@ module MrMurano
386
386
 
387
387
  ## Get one device
388
388
  # @param id [String] The identity to fetch
389
- def fetch(id)
389
+ def fetch(id, _untainted=false)
390
390
  get("/#{CGI.escape(id.to_s)}")
391
391
  end
392
392
 
@@ -22,14 +22,12 @@ module MrMurano
22
22
  include Http
23
23
  include Verbose
24
24
 
25
+ attr_accessor :logging_on
25
26
  attr_accessor :login_info
26
27
  attr_reader :token_biz
27
28
 
28
29
  def initialize
29
- @login_info = nil
30
- @pass_file = nil
31
- @token_file = nil
32
- token_reset
30
+ credentials_reset
33
31
  end
34
32
 
35
33
  def add_headers(request)
@@ -52,6 +50,15 @@ module MrMurano
52
50
  request
53
51
  end
54
52
 
53
+ def credentials_reset
54
+ @logging_on = false
55
+ @login_info = nil
56
+ @pass_file = nil
57
+ @token_file = nil
58
+ token_reset
59
+ @password_user = ''
60
+ end
61
+
55
62
  # ---------------------------------------------------------------------
56
63
 
57
64
  def pass_file
@@ -118,8 +125,10 @@ module MrMurano
118
125
  @token_biz
119
126
  end
120
127
 
121
- def token_reset
122
- @token_biz = ''
128
+ def token_reset(new_token='')
129
+ # Setting the empty token will cause it to be re-fetched
130
+ # on next login_info.
131
+ @token_biz = new_token
123
132
  end
124
133
 
125
134
  def ensure_token!
@@ -140,7 +149,7 @@ module MrMurano
140
149
  acc_token = token_file.lookup(net_host, user_name)
141
150
  if acc_token.to_s.empty?
142
151
  nil
143
- elsif acc_token !~ /^[a-fA-F0-9]+$/
152
+ elsif !token_looks_legit(acc_token)
144
153
  warning "Malformed '#{net_host}:#{user_name}' token: #{acc_token}"
145
154
  nil
146
155
  else
@@ -148,6 +157,10 @@ module MrMurano
148
157
  end
149
158
  end
150
159
 
160
+ def token_looks_legit(acc_token)
161
+ (acc_token =~ /^[a-fA-F0-9]{40}$/) == 0
162
+ end
163
+
151
164
  def token_save(net_host=nil, user_name=nil)
152
165
  net_host, user_name = get_host_user_pair(net_host, user_name)
153
166
  return unless $cfg['auth.persist-token']
data/lib/MrMurano/Logs.rb CHANGED
@@ -5,7 +5,16 @@
5
5
  # vim:tw=0:ts=2:sw=2:et:ai
6
6
  # Unauthorized copying of this file is strictly prohibited.
7
7
 
8
- require 'eventmachine'
8
+ begin
9
+ require 'eventmachine'
10
+ rescue StandardError => _err
11
+ # (lb): I started developing in tmux, and I get this error only within it:
12
+ # require 'eventmachine'
13
+ # Unable to load the EventMachine C extension;
14
+ # To use the pure-ruby reactor, require 'em/pure_ruby'
15
+ require 'em/pure_ruby'
16
+ end
17
+
9
18
  require 'faye/websocket'
10
19
 
11
20
  require 'MrMurano/http'
@@ -8,9 +8,9 @@
8
8
  # 2017-07-01: This ordered list hacks around having
9
9
  # individual files include all the files they need.
10
10
 
11
- require 'yaml'
12
11
  require 'json-schema'
13
12
  require 'pathname'
13
+ require 'yaml'
14
14
  require 'MrMurano/verbosing'
15
15
  require 'MrMurano/Config'
16
16
  require 'MrMurano/hash'
@@ -127,12 +127,29 @@ module Commander
127
127
  hooked.check_run_post_hook
128
128
  end
129
129
 
130
+ alias old_args_without_command_name args_without_command_name
131
+ def args_without_command_name
132
+ args_sans = old_args_without_command_name
133
+ args_sans += ['--'] + @positional unless @positional.empty?
134
+ args_sans
135
+ end
136
+
137
+ alias old_remove_global_options remove_global_options
138
+ def remove_global_options(options, args)
139
+ # Look for sole '--' argument, which signals start of positionals.
140
+ i_positional = args.index('--')
141
+ args = args[0, i_positional] unless i_positional.nil?
142
+ old_remove_global_options(options, args)
143
+ end
144
+
130
145
  alias old_parse_global_options parse_global_options
131
146
  def parse_global_options
132
147
  # User can specify, e.g., status.options, to set options for specific commands.
133
148
  defopts = ($cfg["#{active_command.name}.options"] || '').split
134
149
  @args.push(*defopts)
135
- help_hack
150
+ end_of_options_hack
151
+ do_help = help_hack
152
+ $cfg.validate_cmd_business_and_project(active_command) unless do_help
136
153
  parse_global_options_real
137
154
  end
138
155
 
@@ -150,12 +167,21 @@ module Commander
150
167
  exit 2
151
168
  end
152
169
 
170
+ def end_of_options_hack
171
+ @positional = []
172
+ i_positional = @args.index('--')
173
+ return if i_positional.nil?
174
+ @positional = @args[i_positional + 1..-1]
175
+ @args = @args[0, i_positional]
176
+ end
177
+
153
178
  # 2017-08-22: Commander's help infrastructure is either really weak,
154
179
  # or we did something elsewhere that seriously cripples it. In any
155
180
  # case, this fixes all its quirks.
156
181
  def help_hack
157
- do_help = fix_args
182
+ do_help = fix_args_for_help
158
183
  show_alias_help_maybe! if do_help
184
+ do_help
159
185
  end
160
186
 
161
187
  def help_opts
@@ -166,7 +192,7 @@ module Commander
166
192
  %w[-v --version].freeze
167
193
  end
168
194
 
169
- def fix_args
195
+ def fix_args_for_help
170
196
  # If `murano --help` is specified, let rb-commander handle it.
171
197
  # But if `murano command --help` is specified, don't let cmdr
172
198
  # handle it, otherwise it just shows the command description,
@@ -175,79 +201,110 @@ module Commander
175
201
  # 'help' might really be a command argument (e.g., a solution name).
176
202
  # Note: `murano -v`'s active_command is 'help'.
177
203
  do_help = (@args & %w[-h --help]).any? || active_command.name == 'help'
178
- if do_help
179
- # If there are options in addition to --help, then Commander
180
- # runs the command! So remove all options.
181
- #
182
- # E.g., this shows the help for usage:
183
- # $ murano --help usage
184
- # but if a flag is added, the command gets run, e.g.,
185
- # $ murano usage --id 1234 --help
186
- # runs the usage command.
187
- #
188
- # I [lb] walked the code and it looks like when Commander tries to
189
- # validate the args, it doesn't like the --id flag (maybe because
190
- # the "help" command is being used to validate, and it does not
191
- # define any options?). Then it removes the --id switch (after the
192
- # --help switch was previously removed) and tries the command *again*
193
- # (in gems/commander-4.4.3/lib/commander/runner.rb, look for the
194
- # comment, "Remove the offending args and retry"). So in the example
195
- # given above, `murano usage --id 1234 --help`, both the `--id` flag
196
- # and `--help` flag are moved from @args, and then `murano usage 1234`
197
- # is executed (and args are not validated by Commander but merely passed
198
- # to the command action, so `usage` gets args=['1234']). Whadda wreck.
199
- reject_next = false
200
- @args.reject! do |arg|
201
- reject = false
202
- if reject_next
203
- reject = true
204
- reject_next = false
205
- elsif arg.start_with?('-') && !help_opts.include?(arg) && !vers_opts.include?(arg)
206
- reject = true
207
- # See if the next argument should also be consumed.
208
- arg_opts = @options.select do |opt|
209
- if arg =~ /^-[^-]/
210
- # Single char abbrev: look for exact match.
211
- opt[:switches].include?(arg)
212
- else
213
- # Long switch: Commander matches abbrevs of longs...
214
- opt[:switches].any? { |oarg| oarg =~ /^#{arg}/ }
215
- end
216
- end
217
- if arg =~ /^--/
218
- exact = arg_opts.select do |opt|
219
- opt[:switches].include?(arg) || opt[:switches].any? do |sw|
220
- sw.start_with?(arg + ' ')
221
- end
222
- end
223
- arg_opts = exact unless exact.empty?
224
- end
225
- if arg_opts.length > 1
226
- # MAYBE/2017-08-23: Always do this check, not just for help.
227
- ambig = MrMurano::Verbose.fancy_ticks(arg)
228
- match = arg_opts.map do |opt|
229
- opt[:switches].map { |sw| MrMurano::Verbose.fancy_ticks(sw) }.join('|')
230
- end
231
- match = match.flatten
232
- match[-1] = "and #{match[-1]}" if match.length > 1
233
- match = match.join(', ')
234
- MrMurano::Verbose.error("Ambiguous option: #{ambig} matches: #{match}")
235
- exit 2
236
- elsif arg_opts.length == 1
237
- if arg_opts.first[:switches].any? { |opt| opt.start_with?('--') && opt.include?(' ') }
238
- # There's a space in the --long setting, e.g., '--config KEY=VAL',
239
- # so we know there's an argument following.
240
- reject_next = true
241
- end
242
- end
243
- end
244
- reject
245
- end
246
- end
204
+ trim_options_from_args if do_help
247
205
  @purargs = @args - help_opts
248
206
  return do_help if active_command.name == 'help'
249
207
  # Any command other than `murano help` or `murano --help`.
250
208
  return do_help if !do_help || active_command.name.include?(' ')
209
+ # Command is not --help, or it's help for a single-word command.
210
+ cleanup_args_simple_command_help
211
+ do_help
212
+ end
213
+
214
+ # If there are options in addition to --help, then Commander
215
+ # runs the command! So remove all options.
216
+ #
217
+ # E.g., this shows the help for usage:
218
+ #
219
+ # $ murano --help usage
220
+ #
221
+ # but if a flag is added, the command gets run, e.g.,
222
+ #
223
+ # $ murano usage --id 1234 --help
224
+ #
225
+ # runs the usage command.
226
+ #
227
+ # (lb): I walked the code and it looks like when Commander tries to
228
+ # validate the args, it doesn't like the --id flag (maybe because
229
+ # the "help" command is being used to validate, and it does not
230
+ # define any options?). Then it removes the --id switch (after the
231
+ # --help switch was previously removed) and tries the command *again*
232
+ # (in gems/commander-4.4.3/lib/commander/runner.rb, look for the
233
+ # comment, "Remove the offending args and retry"). So in the example
234
+ # given above, `murano usage --id 1234 --help`, both the `--id` flag
235
+ # and `--help` flag are moved from @args, and then `murano usage 1234`
236
+ # is executed (and args are not validated by Commander but merely passed
237
+ # to the command action, so `usage` gets args=['1234']). Whadda wreck.
238
+ def trim_options_from_args
239
+ reject_next = false
240
+ @args.reject! do |arg|
241
+ if reject_next
242
+ reject_next = false
243
+ true
244
+ elsif (
245
+ arg.start_with?('-') &&
246
+ !help_opts.include?(arg) &&
247
+ !vers_opts.include?(arg)
248
+ )
249
+ # See if the next argument should also be consumed.
250
+ arg_opts = find_matching_options(arg)
251
+ arg_opts = find_exact_match_maybe(arg, arg_opts)
252
+ reject_next = must_be_sane_option!(arg_opts)
253
+ true
254
+ else
255
+ false
256
+ end
257
+ end
258
+ end
259
+
260
+ def find_matching_options(arg)
261
+ @options.select do |opt|
262
+ if arg =~ /^-[^-]/
263
+ # Single char abbrev: look for exact match.
264
+ opt[:switches].include?(arg)
265
+ else
266
+ # Long switch: Commander matches abbrevs of longs...
267
+ opt[:switches].any? { |oarg| oarg =~ /^#{arg}/ }
268
+ end
269
+ end
270
+ end
271
+
272
+ def find_exact_match_maybe(arg, arg_opts)
273
+ return arg_opts unless arg =~ /^--/
274
+ exact = arg_opts.select do |opt|
275
+ opt[:switches].include?(arg) || opt[:switches].any? do |sw|
276
+ sw.start_with?(arg + ' ')
277
+ end
278
+ end
279
+ exact.empty? && exact || arg_opts
280
+ end
281
+
282
+ # Returns true if next arg is input to current --option.
283
+ def must_be_sane_option!(arg_opts)
284
+ if arg_opts.length > 1
285
+ # MAYBE/2017-08-23: Always do this check, not just for help.
286
+ ambig = MrMurano::Verbose.fancy_ticks(arg)
287
+ match = arg_opts.map do |opt|
288
+ opt[:switches].map { |sw| MrMurano::Verbose.fancy_ticks(sw) }.join('|')
289
+ end
290
+ match = match.flatten
291
+ match[-1] = "and #{match[-1]}" if match.length > 1
292
+ match = match.join(', ')
293
+ MrMurano::Verbose.error("Ambiguous option: #{ambig} matches: #{match}")
294
+ exit 2
295
+ elsif arg_opts.length == 1
296
+ # See if this option (the only option) has input.
297
+ arg_opts.first[:switches].any? do |opt|
298
+ # There may be a space in a --long option, e.g.,
299
+ # '--config KEY=VAL', which means an argument follows.
300
+ opt.start_with?('--') && opt.include?(' ')
301
+ end
302
+ else
303
+ false
304
+ end
305
+ end
306
+
307
+ def cleanup_args_simple_command_help
251
308
  # This is a single-word command, e.g., 'link', not 'link list',
252
309
  # as in `murano link --help`, not `murano link list --help`.
253
310
  # Positional parameters break Commander. E.g.,
@@ -258,7 +315,6 @@ module Commander
258
315
  @args = @args[0..0]
259
316
  # Add back in the --help if the command is not a subcommand help.
260
317
  @args.push('--help') unless active_command.subcmdgrouphelp
261
- do_help
262
318
  end
263
319
 
264
320
  def show_alias_help_maybe!
@@ -21,8 +21,6 @@ module MrMurano
21
21
  return [] unless ret.is_a?(Hash) && !ret.key?(:error)
22
22
  return [] unless ret.key?(:items)
23
23
  ret[:items]
24
- # MAYBE/2017-08-17:
25
- # sort_by_name(ret[:items])
26
24
  end
27
25
 
28
26
  def search(svc_name)
@@ -33,8 +31,14 @@ module MrMurano
33
31
  super(svc_name, path)
34
32
  end
35
33
 
36
- def fetch(id)
37
- get('/' + id.to_s)
34
+ def fetch(id, _untainted=false, &block)
35
+ must_id! id
36
+ get('/' + id.to_s, &block)
37
+ end
38
+
39
+ def must_id!(id)
40
+ return unless id.nil?
41
+ raise "ID not set, or Service '#{@service_name}' not enabled for this Solution"
38
42
  end
39
43
 
40
44
  def scid_for_name(name)
@@ -69,21 +73,24 @@ module MrMurano
69
73
  )
70
74
  end
71
75
 
72
- def remove(id)
76
+ def remove(id, &block)
77
+ must_id! id
73
78
  return unless remove_item_allowed(id)
74
- delete("/#{id}")
79
+ delete("/#{id}", &block)
75
80
  end
76
81
 
77
- def info(id=scid)
78
- get("/#{id}/info")
82
+ def info(id=scid, &block)
83
+ must_id! id
84
+ get("/#{id}/info", &block)
79
85
  end
80
86
 
81
- def logs(id=scid)
82
- get("/#{id}/logs")
87
+ def logs(id=scid, &block)
88
+ must_id! id
89
+ get("/#{id}/logs", &block)
83
90
  end
84
91
 
85
92
  def call(opid, meth=:get, data=nil, id=scid, &block)
86
- raise "Service '#{@service_name}' not enabled for this Solution" if id.nil?
93
+ must_id! id
87
94
  call = "/#{id}/call/#{opid}"
88
95
  debug "Will call: #{call}"
89
96
  case meth