morpheus-cli 2.10.2 → 2.10.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ecd262f3f7122ce8af78b08d5539dc450db6dafa
4
- data.tar.gz: b0c4136d4746c82f05d1602f10797dd632f7c081
3
+ metadata.gz: 63a320542a8d185d0e96a8ca526ff4ff23b21868
4
+ data.tar.gz: aeccbd35966dd37101a032acd9346804767ae627
5
5
  SHA512:
6
- metadata.gz: 40e0e43ff256acc748caa4577e1231b733c2c25e725a52e122b61602b092a5a541464afbcddf7010494f6e1901c973df6fce291a6961c0ed6100f5e076848f29
7
- data.tar.gz: 214f1a02d4d192ec428d13a6bec8d1e33de2aa89f05cf51c8f94e7876bca9169a049fe7c8fbcf649ff357e9ec655dd1be00eec4fb5ea499e897a74e0564a8751
6
+ metadata.gz: c825db622aed4d8d571d97bf13a250333f04543834b7749f57a33555fc17e89a0190797e6c0ec6b06dfdf246f623329c4fd7b3b9aef097ac0160e196f1d80712
7
+ data.tar.gz: fe005b26254bbc9fcf41cde841a5ad40715583d0b637a763e8d0612bdc23341f98df11e9385b98a4d8588fc15c6e53a517d040f9463935be7b2e139141500c84
@@ -81,7 +81,12 @@ end
81
81
 
82
82
  # ok, execute the command (or alias)
83
83
  begin
84
- cmd_result = Morpheus::Cli::CliRegistry.exec(args[0], args[1..-1])
84
+ # shell is a Singleton command class
85
+ if args[0] == "shell"
86
+ cmd_result = Morpheus::Cli::Shell.instance.handle(args[1..-1])
87
+ else
88
+ cmd_result = Morpheus::Cli::CliRegistry.exec(args[0], args[1..-1])
89
+ end
85
90
  if cmd_result == false
86
91
  exit 1
87
92
  else
@@ -27,7 +27,7 @@ class Morpheus::Cli::AliasCommand
27
27
  exit 127
28
28
  elsif self.class.has_subcommand?(args[0])
29
29
  handle_subcommand(args)
30
- elsif args.count == 1
30
+ elsif args.count == 1 || (args.count == 2 && args.include?('-e'))
31
31
  add(args)
32
32
  else
33
33
  handle_subcommand(args)
@@ -80,7 +80,7 @@ class Morpheus::Cli::AliasCommand
80
80
  Morpheus::Cli::CliRegistry.instance.add_alias(alias_name, command_string)
81
81
  #print "registered alias #{alias_name}", "\n"
82
82
  if do_export
83
- puts "exporting alias '#{alias_name}' now..."
83
+ # puts "exporting alias '#{alias_name}' now..."
84
84
  morpheus_profile = Morpheus::Cli::DotFile.new(Morpheus::Cli::DotFile.morpheus_profile_filename)
85
85
  morpheus_profile.export_aliases({(alias_name) => command_string})
86
86
  end
@@ -146,6 +146,9 @@ module Morpheus
146
146
  credential_map[app_name] = token
147
147
  begin
148
148
  fn = credentials_file_path
149
+ if !Dir.exists?(File.dirname(fn))
150
+ FileUtils.mkdir_p(File.dirname(fn))
151
+ end
149
152
  print "#{dark} #=> adding credentials to #{fn}#{reset}\n" if Morpheus::Logging.debug?
150
153
  File.open(fn, 'w') {|f| f.write credential_map.to_yaml } #Store
151
154
  FileUtils.chmod(0600, fn)
@@ -1,3 +1,4 @@
1
+ require 'fileutils'
1
2
  require 'yaml'
2
3
  require 'io/console'
3
4
  require 'rest_client'
@@ -550,8 +551,12 @@ public
550
551
  end
551
552
 
552
553
  def save_groups(groups_map)
553
- File.open(groups_file_path, 'w') {|f| f.write groups_map.to_yaml } #Store
554
- FileUtils.chmod(0600, groups_file_path)
554
+ fn = groups_file_path
555
+ if !Dir.exists?(File.dirname(fn))
556
+ FileUtils.mkdir_p(File.dirname(fn))
557
+ end
558
+ File.open(fn, 'w') {|f| f.write groups_map.to_yaml } #Store
559
+ FileUtils.chmod(0600, fn)
555
560
  @@groups = groups_map
556
561
  end
557
562
 
@@ -48,15 +48,19 @@ class Morpheus::Cli::Login
48
48
  else
49
49
  @appliance_name, @appliance_url = nil, nil
50
50
  end
51
+ if !@appliance_name
52
+ print_red_alert "You have no appliance named '#{options[:remote]}' configured. See the `remote add` command."
53
+ return false
54
+ end
51
55
  else
52
56
  @appliance_name, @appliance_url = Morpheus::Cli::Remote.active_appliance
53
- end
54
-
55
- begin
56
57
  if !@appliance_name
57
58
  print yellow,"Please specify a remote appliance with -r or see the command `remote use`#{reset}\n"
58
59
  return false
59
60
  end
61
+ end
62
+
63
+ begin
60
64
  if options[:quiet]
61
65
  if username.empty? || password.empty?
62
66
  print yellow,"You have not specified username and password\n"
@@ -162,7 +162,7 @@ module Morpheus::Cli::PrintHelper
162
162
 
163
163
  def print_results_pagination(json_response)
164
164
  if json_response && json_response["meta"]
165
- print cyan,"\nViewing #{json_response['meta']['offset'].to_i + 1}-#{json_response['meta']['offset'].to_i + json_response['meta']['size'].to_i} of #{json_response['meta']['total']}\n"
165
+ print cyan,"\nViewing #{json_response['meta']['offset'].to_i + 1}-#{json_response['meta']['offset'].to_i + json_response['meta']['size'].to_i} of #{json_response['meta']['total']}\n", reset
166
166
  end
167
167
  end
168
168
 
@@ -217,29 +217,29 @@ module Morpheus::Cli::ProvisioningHelper
217
217
  end
218
218
 
219
219
  payload = {
220
- :zoneId => cloud_id,
221
- :instance => {
222
- :name => instance_name,
223
- :site => {
224
- :id => group_id
220
+ 'zoneId' => cloud_id,
221
+ 'instance' => {
222
+ 'name' => instance_name,
223
+ 'site' => {
224
+ 'id' => group_id
225
225
  },
226
- :instanceType => {
227
- :code => instance_type_code
226
+ 'instanceType' => {
227
+ 'code' => instance_type_code
228
228
  }
229
229
  }
230
230
  }
231
231
 
232
232
  # Description
233
233
  v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'description', 'fieldLabel' => 'Description', 'type' => 'text', 'required' => false}], options[:options])
234
- payload[:instance][:description] = v_prompt['description'] if !v_prompt['description'].empty?
234
+ payload['instance']['description'] = v_prompt['description'] if !v_prompt['description'].empty?
235
235
 
236
236
  # Environment
237
237
  v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'instanceContext', 'fieldLabel' => 'Environment', 'type' => 'select', 'required' => false, 'selectOptions' => instance_context_options()}], options[:options])
238
- payload[:instance][:instanceContext] = v_prompt['instanceContext'] if !v_prompt['instanceContext'].empty?
238
+ payload['instance']['instanceContext'] = v_prompt['instanceContext'] if !v_prompt['instanceContext'].empty?
239
239
 
240
240
  # Tags
241
241
  v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'tags', 'fieldLabel' => 'Tags', 'type' => 'text', 'required' => false}], options[:options])
242
- payload[:instance][:tags] = v_prompt['tags'].split(',').collect {|it| it.to_s.strip }.compact.uniq if !v_prompt['tags'].empty?
242
+ payload['instance']['tags'] = v_prompt['tags'].split(',').collect {|it| it.to_s.strip }.compact.uniq if !v_prompt['tags'].empty?
243
243
 
244
244
  # Version and Layout
245
245
 
@@ -247,7 +247,11 @@ module Morpheus::Cli::ProvisioningHelper
247
247
  layout_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'layout', 'type' => 'select', 'fieldLabel' => 'Layout', 'optionSource' => 'layoutsForCloud', 'required' => true, 'description' => 'Select which configuration of the instance type to be provisioned.'}],options[:options],api_client,{groupId: group_id, cloudId: cloud_id, instanceTypeId: instance_type['id'], version: version_prompt['version']})
248
248
  layout_id = layout_prompt['layout']
249
249
  layout = instance_type['instanceTypeLayouts'].find{ |lt| lt['id'] == layout_id.to_i}
250
- payload[:instance][:layout] = {id: layout['id']}
250
+ if !layout
251
+ print_red_alert "Layout not found by id #{layout_id}"
252
+ exit 1
253
+ end
254
+ payload['instance']['layout'] = {'id' => layout['id']}
251
255
 
252
256
  # prompt for service plan
253
257
  service_plans_json = @instances_interface.service_plans({zoneId: cloud_id, layoutId: layout_id})
@@ -255,14 +259,12 @@ module Morpheus::Cli::ProvisioningHelper
255
259
  service_plans_dropdown = service_plans.collect {|sp| {'name' => sp["name"], 'value' => sp["id"]} } # already sorted
256
260
  plan_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'servicePlan', 'type' => 'select', 'fieldLabel' => 'Plan', 'selectOptions' => service_plans_dropdown, 'required' => true, 'description' => 'Choose the appropriately sized plan for this instance'}],options[:options])
257
261
  service_plan = service_plans.find {|sp| sp["id"] == plan_prompt['servicePlan'].to_i }
258
- # todo: pick one of these three, let's go with the last one...
259
- #payload[:servicePlan] = service_plan["id"] # pre-2.10 appliances
260
- payload[:instance][:plan] = {id: service_plan["id"]}
262
+ payload['instance']['plan'] = {'id' => service_plan["id"]}
261
263
 
262
264
  # prompt for volumes
263
265
  volumes = prompt_volumes(service_plan, options, api_client, {})
264
266
  if !volumes.empty?
265
- payload[:volumes] = volumes
267
+ payload['volumes'] = volumes
266
268
  end
267
269
 
268
270
  if layout["provisionType"] && layout["provisionType"]["id"] && layout["provisionType"]["hasNetworks"]
@@ -270,46 +272,42 @@ module Morpheus::Cli::ProvisioningHelper
270
272
  begin
271
273
  network_interfaces = prompt_network_interfaces(cloud_id, layout["provisionType"]["id"], options)
272
274
  if !network_interfaces.empty?
273
- payload[:networkInterfaces] = network_interfaces
275
+ payload['networkInterfaces'] = network_interfaces
274
276
  end
275
277
  rescue RestClient::Exception => e
276
278
  print_yellow_warning "Unable to load network options. Proceeding..."
277
279
  print_rest_exception(e, options) if Morpheus::Logging.debug?
278
280
  end
279
281
  end
280
- type_payload = {}
282
+
283
+
281
284
  if !layout['optionTypes'].nil? && !layout['optionTypes'].empty?
282
- type_payload = Morpheus::Cli::OptionTypes.prompt(layout['optionTypes'],options[:options],api_client,{groupId: group_id, cloudId: cloud_id, zoneId: cloud_id, instanceTypeId: instance_type['id'], version: version_prompt['version']})
285
+ type_payload = Morpheus::Cli::OptionTypes.prompt(layout['optionTypes'],options[:options],@api_client,{groupId: group_id, cloudId: cloud_id, zoneId: cloud_id, instanceTypeId: instance_type['id'], version: version_prompt['version']})
286
+ payload.deep_merge!(type_payload)
283
287
  elsif !instance_type['optionTypes'].nil? && !instance_type['optionTypes'].empty?
284
- type_payload = Morpheus::Cli::OptionTypes.prompt(instance_type['optionTypes'],options[:options],api_client,{groupId: group_id, cloudId: cloud_id, zoneId: cloud_id, instanceTypeId: instance_type['id'], version: version_prompt['version']})
288
+ type_payload = Morpheus::Cli::OptionTypes.prompt(instance_type['optionTypes'],options[:options],@api_client,{groupId: group_id, cloudId: cloud_id, zoneId: cloud_id, instanceTypeId: instance_type['id'], version: version_prompt['version']})
289
+ payload.deep_merge!(type_payload)
285
290
  end
286
- if !type_payload['config'].nil?
287
- payload.merge!(type_payload['config'])
288
- end
289
-
290
- provision_payload = {}
291
+
291
292
  if !layout['provisionType'].nil? && !layout['provisionType']['optionTypes'].nil? && !layout['provisionType']['optionTypes'].empty?
292
293
  instance_type_option_types = layout['provisionType']['optionTypes']
293
294
  # remove volume options if volumes were configured
294
- if !payload[:volumes].empty?
295
+ if !payload['volumes'].empty?
295
296
  instance_type_option_types = reject_volume_option_types(instance_type_option_types)
296
297
  end
297
298
  # remove networkId option if networks were configured above
298
- if !payload[:networkInterfaces].empty?
299
+ if !payload['networkInterfaces'].empty?
299
300
  instance_type_option_types = reject_networking_option_types(instance_type_option_types)
300
301
  end
302
+ #print "#{dark} #=> gathering instance type option types for layout provision type...#{reset}\n" if Morpheus::Logging.debug?
301
303
  provision_payload = Morpheus::Cli::OptionTypes.prompt(instance_type_option_types,options[:options],api_client,{groupId: group_id, cloudId: cloud_id, zoneId: cloud_id, instanceTypeId: instance_type['id'], version: version_prompt['version']})
302
- end
303
-
304
- payload[:config] = provision_payload['config'] || {}
305
- if provision_payload['server']
306
- payload[:server] = provision_payload['server'] || {}
304
+ payload.deep_merge!(provision_payload)
307
305
  end
308
306
 
309
307
  # prompt for environment variables
310
308
  evars = prompt_evars(options)
311
309
  if !evars.empty?
312
- payload[:evars] = evars
310
+ payload['evars'] = evars
313
311
  end
314
312
 
315
313
  return payload
@@ -844,10 +842,10 @@ module Morpheus::Cli::ProvisioningHelper
844
842
  network_interface['network']['id'] = v_prompt[field_context]['networkId'].to_i
845
843
  selected_network = networks.find {|it| it["id"] == network_interface['network']['id'] }
846
844
 
847
- # if !selected_network
848
- # print_red_alert "Network not found by id #{network_interface['network']['id']}!"
849
- # exit 1
850
- # end
845
+ if !selected_network
846
+ print_red_alert "Network not found by id #{network_interface['network']['id']}!"
847
+ exit 1
848
+ end
851
849
 
852
850
  # choose network interface type
853
851
  if enable_network_type_selection && !network_interface_type_options.empty?
@@ -41,23 +41,35 @@ module Morpheus
41
41
  value = nil
42
42
  value_found=false
43
43
  if option_type['fieldContext']
44
- results[option_type['fieldContext']] ||= {}
45
- context_map = results[option_type['fieldContext']]
46
- if options[option_type['fieldContext']] and options[option_type['fieldContext']].key?(option_type['fieldName'])
47
- value = options[option_type['fieldContext']][option_type['fieldName']]
44
+ cur_namespace = options
45
+ namespaces = option_type['fieldContext'].split(".")
46
+ namespaces.each do |ns|
47
+ next if ns.empty?
48
+ cur_namespace[ns.to_s] ||= {}
49
+ cur_namespace = cur_namespace[ns.to_s]
50
+ context_map[ns.to_s] ||= {}
51
+ context_map = context_map[ns.to_s]
52
+ end
53
+ # use the value passed in the options map
54
+ if cur_namespace.key?(option_type['fieldName'])
55
+ value = cur_namespace[option_type['fieldName']]
48
56
  if option_type['type'] == 'number'
49
57
  value = value.to_i
58
+ elsif option_type['type'] == 'select'
59
+ # this should just fall down through below, with the extra params no_prompt, use_value
60
+ value = select_prompt(option_type, api_client, api_params, true, value)
50
61
  end
51
62
  value_found = true
52
63
  end
53
- end
54
-
55
- if value_found == false && options.key?(option_type['fieldName'])
56
- value = options[option_type['fieldName']]
57
- if option_type['type'] == 'number'
58
- value = value.to_i
64
+ else
65
+ # no fieldContext
66
+ if value_found == false && options.key?(option_type['fieldName'])
67
+ value = options[option_type['fieldName']]
68
+ if option_type['type'] == 'number'
69
+ value = value.to_i
70
+ end
71
+ value_found = true
59
72
  end
60
- value_found = true
61
73
  end
62
74
 
63
75
  # no_prompt means skip prompting and instead
@@ -78,8 +90,8 @@ module Morpheus
78
90
  end
79
91
  if !value_found
80
92
  if option_type['required']
81
- print Term::ANSIColor.red, "\nMissing Required Option\n\n"
82
- print Term::ANSIColor.red, " * #{option_type['fieldLabel']} [-O #{option_type['fieldContext'] ? (option_type['fieldContext']+'.') : ''}#{option_type['fieldName']}=] - ", Term::ANSIColor.reset , "#{option_type['description']}\n"
93
+ print Term::ANSIColor.red, "\nMissing Required Option\n\n", Term::ANSIColor.reset
94
+ print Term::ANSIColor.red, " * #{option_type['fieldLabel']} [-O #{option_type['fieldContext'] ? (option_type['fieldContext']+'.') : ''}#{option_type['fieldName']}=] - #{option_type['description']}\n", Term::ANSIColor.reset
83
95
  print "\n"
84
96
  exit 1
85
97
  else
@@ -190,25 +202,41 @@ module Morpheus
190
202
  return value
191
203
  end
192
204
 
193
- def self.select_prompt(option_type,api_client, api_params={}, no_prompt=false)
205
+ def self.select_prompt(option_type,api_client, api_params={}, no_prompt=false, use_value=nil)
194
206
  value_found = false
195
207
  value = nil
208
+ # local array of options
196
209
  if option_type['selectOptions']
197
210
  select_options = option_type['selectOptions']
211
+ # remote optionSource aka /api/options/$optionSource?
198
212
  elsif option_type['optionSource']
199
213
  select_options = load_source_options(option_type['optionSource'],api_client,api_params)
200
214
  else
201
215
  raise "select_prompt() requires selectOptions or optionSource!"
202
216
  end
203
- if !select_options.nil? && select_options.count == 1 && option_type['skipSingleOption'] == true
217
+ # ensure the preselected value (passed as an option) is in the dropdown
218
+ if !use_value.nil?
219
+ matched_value = select_options.find {|opt| opt['value'].to_s == use_value.to_s }
220
+ if !matched_value.nil?
221
+ value = use_value
222
+ value_found = true
223
+ else
224
+ print Term::ANSIColor.red, "\nInvalid Option #{option_type['fieldLabel']}: [#{use_value}]\n\n", Term::ANSIColor.reset
225
+ print Term::ANSIColor.red, " * #{option_type['fieldLabel']} [-O #{option_type['fieldContext'] ? (option_type['fieldContext']+'.') : ''}#{option_type['fieldName']}=] - #{option_type['description']}\n", Term::ANSIColor.reset
226
+ display_select_options(select_options)
227
+ print "\n"
228
+ exit 1
229
+ end
230
+ elsif !select_options.nil? && select_options.count == 1 && option_type['skipSingleOption'] == true
204
231
  value_found = true
205
232
  value = select_options[0]['value']
206
233
  end
234
+
207
235
  if no_prompt
208
236
  if !value_found
209
237
  if option_type['required']
210
- print Term::ANSIColor.red, "\nMissing Required Option\n\n"
211
- print Term::ANSIColor.red, " * #{option_type['fieldLabel']} [-O #{option_type['fieldContext'] ? (option_type['fieldContext']+'.') : ''}#{option_type['fieldName']}=] - ", Term::ANSIColor.reset , "#{option_type['description']}\n"
238
+ print Term::ANSIColor.red, "\nMissing Required Option\n\n", Term::ANSIColor.reset
239
+ print Term::ANSIColor.red, " * #{option_type['fieldLabel']} [-O #{option_type['fieldContext'] ? (option_type['fieldContext']+'.') : ''}#{option_type['fieldName']}=] - #{option_type['description']}\n", Term::ANSIColor.reset
212
240
  display_select_options(select_options)
213
241
  print "\n"
214
242
  exit 1
@@ -369,7 +397,6 @@ module Morpheus
369
397
  select_options.each do |option|
370
398
  puts " * #{option['name']} [#{option['value']}]"
371
399
  end
372
- puts "\n\n"
373
400
  end
374
401
 
375
402
  def self.format_option_types_help(option_types)
@@ -475,8 +475,12 @@ class Morpheus::Cli::Remote
475
475
  end
476
476
 
477
477
  def save_appliances(new_config)
478
- File.open(appliances_file_path, 'w') {|f| f.write new_config.to_yaml } #Store
479
- FileUtils.chmod(0600, appliances_file_path)
478
+ fn = appliances_file_path
479
+ if !Dir.exists?(File.dirname(fn))
480
+ FileUtils.mkdir_p(File.dirname(fn))
481
+ end
482
+ File.open(fn, 'w') {|f| f.write new_config.to_yaml } #Store
483
+ FileUtils.chmod(0600, fn)
480
484
  #@@appliance_config = load_appliance_file
481
485
  @@appliance_config = new_config
482
486
  end
@@ -119,14 +119,14 @@ class Morpheus::Cli::Shell
119
119
  execute_command(cmd)
120
120
  end
121
121
  # skip logging of exit and !cmd
122
- unless input.strip.empty? || (["exit"].include?(input.strip)) || input.strip[0].to_s.chr == "!"
122
+ unless input.strip.empty? || (["exit", "history"].include?(input.strip)) || input.strip[0].to_s.chr == "!"
123
123
  log_history_command(input.strip)
124
124
  end
125
125
  end
126
126
 
127
127
  def execute_command(input)
128
128
  #puts "shell execute_command(#{input})"
129
- @command_options = {}
129
+ @command_options ||= {}
130
130
 
131
131
  input = input.to_s.strip
132
132
 
@@ -213,29 +213,35 @@ class Morpheus::Cli::Shell
213
213
  elsif input =~ /^\!.+/
214
214
  cmd_number = input.sub("!", "").to_i
215
215
  if cmd_number != 0
216
- input = @history[cmd_number]
217
- if !input
216
+ old_input = @history[cmd_number]
217
+ if !old_input
218
218
  puts "Command not found by number #{cmd_number}"
219
219
  return 0
220
220
  end
221
- #puts "executing history command: (#{cmd_number}) #{input}"
222
- execute_commands(input)
223
- return 0
221
+ #puts "executing history command: (#{cmd_number}) #{old_input}"
222
+ # log_history_command(old_input)
223
+ # remove this from readline, and replace it with the old command
224
+ Readline::HISTORY.pop
225
+ Readline::HISTORY << old_input
226
+ return execute_commands(old_input)
224
227
  end
225
228
 
226
229
  elsif input == "insecure"
227
230
  Morpheus::RestClient.enable_ssl_verification = false
228
231
  return 0
232
+
229
233
  # use log-level [debug|info]
230
234
  # elsif input =~ /^log_level/ # hidden for now
231
235
  # log_level = input.sub(/^log_level\s*/, '').strip
232
236
  # if log_level == ""
233
237
  # puts "#{Morpheus::Logging.log_level}"
234
- # elsif log_level == "debug"
235
- # #log_history_command(input)
236
- # @command_options[:debug] = true
237
- # Morpheus::Logging.set_log_level(Morpheus::Logging::Logger::DEBUG)
238
- # ::RestClient.log = Morpheus::Logging.debug? ? STDOUT : nil
238
+ elsif input == "debug"
239
+ log_history_command(input)
240
+ Morpheus::Cli::LogLevelCommand.new.handle(["debug"])
241
+ @command_options[:debug] = true
242
+ return 0
243
+ # Morpheus::Logging.set_log_level(Morpheus::Logging::Logger::DEBUG)
244
+ # ::RestClient.log = Morpheus::Logging.debug? ? STDOUT : nil
239
245
  # elsif log_level == "info"
240
246
  # #log_history_command(input)
241
247
  # @command_options.delete(:debug)
@@ -258,7 +264,7 @@ class Morpheus::Cli::Shell
258
264
  # return 0
259
265
  elsif ["hello","hi","hey","hola"].include?(input.strip.downcase)
260
266
  print "#{input.capitalize}, how may I #{cyan}help#{reset} you?\n"
261
- return
267
+ return 0
262
268
  # use morpheus coloring [on|off]
263
269
  # elsif input == "colorize"
264
270
  # Term::ANSIColor::coloring = true
@@ -1,6 +1,6 @@
1
1
 
2
2
  module Morpheus
3
3
  module Cli
4
- VERSION = "2.10.2"
4
+ VERSION = "2.10.3"
5
5
  end
6
6
  end
@@ -0,0 +1,42 @@
1
+ # Provide deep_merge.
2
+ # Borrowed from rails active_support
3
+ # https://github.com/rails/rails/blob/master/activesupport/lib/active_support/core_ext/hash/deep_merge.rb
4
+ #
5
+ class Hash
6
+ # Returns a new hash with +self+ and +other_hash+ merged recursively.
7
+ #
8
+ # h1 = { a: true, b: { c: [1, 2, 3] } }
9
+ # h2 = { a: false, b: { x: [3, 4, 5] } }
10
+ #
11
+ # h1.deep_merge(h2) # => { a: false, b: { c: [1, 2, 3], x: [3, 4, 5] } }
12
+ #
13
+ # Like with Hash#merge in the standard library, a block can be provided
14
+ # to merge values:
15
+ #
16
+ # h1 = { a: 100, b: 200, c: { c1: 100 } }
17
+ # h2 = { b: 250, c: { c1: 200 } }
18
+ # h1.deep_merge(h2) { |key, this_val, other_val| this_val + other_val }
19
+ # # => { a: 100, b: 450, c: { c1: 300 } }
20
+ def deep_merge(other_hash, &block)
21
+ dup.deep_merge!(other_hash, &block)
22
+ end
23
+
24
+ # Same as +deep_merge+, but modifies +self+.
25
+ def deep_merge!(other_hash, &block)
26
+ other_hash.each_pair do |current_key, other_value|
27
+ this_value = self[current_key]
28
+
29
+ self[current_key] = if this_value.is_a?(Hash) && other_value.is_a?(Hash)
30
+ this_value.deep_merge(other_value, &block)
31
+ else
32
+ if block_given? && key?(current_key)
33
+ block.call(current_key, this_value, other_value)
34
+ else
35
+ other_value
36
+ end
37
+ end
38
+ end
39
+
40
+ self
41
+ end
42
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: morpheus-cli
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.10.2
4
+ version: 2.10.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Estes
@@ -11,7 +11,7 @@ authors:
11
11
  autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
- date: 2017-03-07 00:00:00.000000000 Z
14
+ date: 2017-03-14 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: bundler
@@ -218,6 +218,7 @@ files:
218
218
  - lib/morpheus/cli/virtual_images.rb
219
219
  - lib/morpheus/cli/whoami.rb
220
220
  - lib/morpheus/cli/workflows.rb
221
+ - lib/morpheus/ext/hash.rb
221
222
  - lib/morpheus/ext/nil_class.rb
222
223
  - lib/morpheus/formatters.rb
223
224
  - lib/morpheus/logging.rb