morpheus-cli 5.2.0 → 5.2.4.1

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.
@@ -19,6 +19,7 @@ class Morpheus::Cli::Instances
19
19
  :history, {:'history-details' => :history_details}, {:'history-event' => :history_event_details},
20
20
  :stats, :stop, :start, :restart, :actions, :action, :suspend, :eject, :stop_service, :start_service, :restart_service,
21
21
  :backup, :backups, :resize, :clone, :envs, :setenv, :delenv,
22
+ :lock, :unlock, :clone_image,
22
23
  :security_groups, :apply_security_groups, :run_workflow, :import_snapshot, :snapshot, :snapshots,
23
24
  :console, :status_check, {:containers => :list_containers},
24
25
  :scaling, {:'scaling-update' => :scaling_update},
@@ -459,17 +460,58 @@ class Morpheus::Cli::Instances
459
460
  options[:instance_name] = args[0]
460
461
  end
461
462
 
462
- # use active group by default
463
- options[:group] ||= @active_group_id
464
- options[:select_datastore] = true
465
- options[:name_required] = true
466
463
  begin
467
464
  payload = nil
468
465
  if options[:payload]
469
466
  payload = options[:payload]
470
467
  # support -O OPTION switch on top of --payload
471
468
  payload.deep_merge!(options[:options].reject {|k,v| k.is_a?(Symbol) }) if options[:options]
469
+ # obviously should support every option that prompt supports on top of -- payload as well
470
+ # group, cloud and type for now
471
+ # todo: also support :layout, service_plan, :resource_pool, etc.
472
+ group = nil
473
+ if options[:group]
474
+ group = find_group_by_name_or_id_for_provisioning(options[:group])
475
+ if group.nil?
476
+ return 1, "group not found by #{options[:group]}"
477
+ end
478
+ #payload["siteId"] = group["id"]
479
+ payload.deep_merge!({"instance" => {"site" => {"id" => group["id"]} } })
480
+ end
481
+ if options[:cloud]
482
+ group_id = group ? group["id"] : ((payload["instance"] && payload["instance"]["site"].is_a?(Hash)) ? payload["instance"]["site"]["id"] : nil)
483
+ cloud = find_cloud_by_name_or_id_for_provisioning(group_id, options[:cloud])
484
+ if cloud.nil?
485
+ return 1, "cloud not found by #{options[:cloud]}"
486
+ end
487
+ payload["zoneId"] = cloud["id"]
488
+ payload.deep_merge!({"instance" => {"cloud" => cloud["name"] } })
489
+ end
490
+ if options[:cloud]
491
+ group_id = group ? group["id"] : ((payload["instance"] && payload["instance"]["site"].is_a?(Hash)) ? payload["instance"]["site"]["id"] : nil)
492
+ cloud = find_cloud_by_name_or_id_for_provisioning(group_id, options[:cloud])
493
+ if cloud.nil?
494
+ return 1, "cloud not found by #{options[:cloud]}"
495
+ end
496
+ payload["zoneId"] = cloud["id"]
497
+ payload.deep_merge!({"instance" => {"cloud" => cloud["name"] } })
498
+ end
499
+ if options[:instance_type_code]
500
+ # should just use find_instance_type_by_name_or_id
501
+ # note that the api actually will match name name or code
502
+ instance_type = (options[:instance_type_code].to_s =~ /\A\d{1,}\Z/) ? find_instance_type_by_id(options[:instance_type_code]) : find_instance_type_by_code(options[:instance_type_code])
503
+ if instance_type.nil?
504
+ return 1, "instance type not found by #{options[:cloud]}"
505
+ end
506
+ payload.deep_merge!({"instance" => {"type" => instance_type["code"] } })
507
+ payload.deep_merge!({"instance" => {"instanceType" => {"code" => instance_type["code"]} } })
508
+ end
509
+
472
510
  else
511
+ # use active group by default
512
+ options[:group] ||= @active_group_id
513
+ options[:select_datastore] = true
514
+ options[:name_required] = true
473
515
  # prompt for all the instance configuration options
474
516
  # this provisioning helper method handles all (most) of the parsing and prompting
475
517
  # and it relies on the method to exit non-zero on error, like a bad CLOUD or TYPE value
@@ -562,16 +604,17 @@ class Morpheus::Cli::Instances
562
604
  opts.on('--group GROUP', String, "Group Name or ID") do |val|
563
605
  options[:group] = val
564
606
  end
565
- opts.on('--tags LIST', String, "Metadata tags in the format 'ping=pong,flash=bang'") do |val|
566
- options[:metadata] = val
607
+ opts.on('--labels [LIST]', String, "Labels (keywords) in the format 'foo, bar'") do |val|
608
+ params['labels'] = val.to_s.split(',').collect {|it| it.to_s.strip }.compact.uniq.join(',')
567
609
  end
568
- opts.on('--metadata LIST', String, "Metadata tags in the format 'ping=pong,flash=bang'") do |val|
569
- options[:metadata] = val
610
+ opts.on('--tags LIST', String, "Tags in the format 'name:value, name:value'. This will add and remove tags.") do |val|
611
+ options[:tags] = val
570
612
  end
571
- opts.add_hidden_option('--metadata')
572
- opts.on('--labels LIST', String, "Labels (keywords) in the format 'foo, bar'") do |val|
573
- # todo switch this from 'tags' to 'labels'
574
- params['tags'] = val.split(',').collect {|it| it.to_s.strip }.compact.uniq.join(',')
613
+ opts.on('--add-tags TAGS', String, "Add Tags in the format 'name:value, name:value'. This will only add/update tags.") do |val|
614
+ options[:add_tags] = val
615
+ end
616
+ opts.on('--remove-tags TAGS', String, "Remove Tags in the format 'name, name:value'. This removes tags, the :value component is optional and must match if passed.") do |val|
617
+ options[:remove_tags] = val
575
618
  end
576
619
  opts.on('--power-schedule-type ID', String, "Power Schedule Type ID") do |val|
577
620
  params['powerScheduleType'] = val == "null" ? nil : val
@@ -627,32 +670,17 @@ class Morpheus::Cli::Instances
627
670
  payload['instance']['site'] = {'id' => group['id']}
628
671
  end
629
672
  # metadata tags
630
- # if options[:options]['metadata'].is_a?(Array) && !options[:metadata]
631
- # options[:metadata] = options[:options]['metadata']
632
- # end
633
- if options[:metadata]
634
- metadata = []
635
- if options[:metadata] == "[]" || options[:metadata] == "null"
636
- payload['instance']['metadata'] = []
637
- elsif options[:metadata].is_a?(Array)
638
- payload['instance']['metadata'] = options[:metadata]
639
- else
640
- # parse string into format name:value, name:value
641
- # merge IDs from current metadata
642
- # todo: should allow quoted semicolons..
643
- metadata_list = options[:metadata].split(",").select {|it| !it.to_s.empty? }
644
- metadata_list = metadata_list.collect do |it|
645
- metadata_pair = it.split(":")
646
- if metadata_pair.size == 1 && it.include?("=")
647
- metadata_pair = it.split("=")
648
- end
649
- row = {}
650
- row['name'] = metadata_pair[0].to_s.strip
651
- row['value'] = metadata_pair[1].to_s.strip
652
- row
653
- end
654
- payload['instance']['metadata'] = metadata_list
655
- end
673
+ if options[:tags]
674
+ # api version 4.2.5 and later supports tags, older versions expect metadata
675
+ # todo: use tags instead like everywhere else
676
+ # payload['instance']['tags'] = parse_metadata(options[:tags])
677
+ payload['instance']['metadata'] = parse_metadata(options[:tags])
678
+ end
679
+ if options[:add_tags]
680
+ payload['instance']['addTags'] = parse_metadata(options[:add_tags])
681
+ end
682
+ if options[:remove_tags]
683
+ payload['instance']['removeTags'] = parse_metadata(options[:remove_tags])
656
684
  end
657
685
  if payload['instance'].empty? && params.empty? && options[:owner].nil?
658
686
  raise_command_error "Specify at least one option to update.\n#{optparse}"
@@ -1256,11 +1284,14 @@ class Morpheus::Cli::Instances
1256
1284
  # metadata tags used to be returned as metadata and are now returned as tags
1257
1285
  # the problem is tags is what we used to call Labels (keywords)
1258
1286
  # the api will change to tags and labels, so handle the old format as long as metadata is returned.
1259
- tags, labels = nil, nil
1260
- if instance.key?('metadata')
1261
- tags, labels = instance['metadata'], instance['tags']
1287
+ labels = nil
1288
+ tags = nil
1289
+ if instance.key?('labels')
1290
+ labels = instance['labels']
1291
+ tags = instance['tags']
1262
1292
  else
1263
- tags, labels = instance['tags'], instance['labels']
1293
+ labels = instance['tags']
1294
+ tags = instance['metadata']
1264
1295
  end
1265
1296
  # containers are fetched via separate api call
1266
1297
  containers = nil
@@ -1302,7 +1333,7 @@ class Morpheus::Cli::Instances
1302
1333
  # "Cost" => lambda {|it| it['hourlyCost'] ? format_money(it['hourlyCost'], (it['currency'] || 'USD'), {sigdig:15}).to_s + ' per hour' : '' },
1303
1334
  # "Price" => lambda {|it| it['hourlyPrice'] ? format_money(it['hourlyPrice'], (it['currency'] || 'USD'), {sigdig:15}).to_s + ' per hour' : '' },
1304
1335
  "Environment" => 'instanceContext',
1305
- "Labels" => lambda {|it| it['tags'] ? it['tags'].join(',') : '' },
1336
+ "Labels" => lambda {|it| labels ? labels.join(',') : '' },
1306
1337
  "Tags" => lambda {|it| tags ? tags.collect {|m| "#{m['name']}: #{m['value']}" }.join(', ') : '' },
1307
1338
  "Owner" => lambda {|it|
1308
1339
  if it['owner']
@@ -1320,6 +1351,7 @@ class Morpheus::Cli::Instances
1320
1351
  "Shutdown Date" => lambda {|it| it['shutdownDate'] ? format_local_dt(it['shutdownDate']) : '' },
1321
1352
  "Nodes" => lambda {|it| it['containers'] ? it['containers'].count : 0 },
1322
1353
  "Connection" => lambda {|it| format_instance_connection_string(it) },
1354
+ "Locked" => lambda {|it| format_boolean(it['locked']) },
1323
1355
  "Status" => lambda {|it| format_instance_status(it) }
1324
1356
  }
1325
1357
  description_cols.delete("Labels") if labels.nil? || labels.empty?
@@ -1329,6 +1361,7 @@ class Morpheus::Cli::Instances
1329
1361
  description_cols.delete("Shutdown Date") if instance['shutdownDate'].nil?
1330
1362
  description_cols["Removal Date"] = lambda {|it| format_local_dt(it['removalDate'])} if instance['status'] == 'pendingRemoval'
1331
1363
  description_cols.delete("Last Deployment") if instance['lastDeploy'].nil?
1364
+ description_cols.delete("Locked") if instance['locked'] != true
1332
1365
  #description_cols.delete("Environment") if instance['instanceContext'].nil?
1333
1366
  print_description_list(description_cols, instance)
1334
1367
 
@@ -3020,7 +3053,8 @@ EOT
3020
3053
  snapshot_column_definitions = {
3021
3054
  "ID" => lambda {|it| it['id'] },
3022
3055
  "Name" => lambda {|it| it['name'] },
3023
- "Description" => lambda {|it| it['snapshotType'] ? (it['snapshotType']['name'] || it['snapshotType']['code']) : '' },
3056
+ "Description" => lambda {|it| it['description'] },
3057
+ # "Type" => lambda {|it| it['snapshotType'] },
3024
3058
  "Date Created" => lambda {|it| format_local_dt(it['snapshotCreated']) },
3025
3059
  "Status" => lambda {|it| format_snapshot_status(it) }
3026
3060
  }
@@ -3857,6 +3891,117 @@ EOT
3857
3891
  return 0
3858
3892
  end
3859
3893
 
3894
+ def clone_image(args)
3895
+ options = {}
3896
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
3897
+ opts.banner = subcommand_usage("[instance]")
3898
+ opts.on( '--name VALUE', String, "Image Name (Template Name). Default is server name + timestamp" ) do |val|
3899
+ options[:options]['templateName'] = val
3900
+ end
3901
+ build_standard_update_options(opts, options)
3902
+ opts.footer = <<-EOT
3903
+ Clone to image (template) for an instance
3904
+ [instance] is required. This is the name or id of an instance
3905
+ EOT
3906
+ end
3907
+ optparse.parse!(args)
3908
+ verify_args!(args:args, optparse:optparse, count:1)
3909
+ connect(options)
3910
+ instance = find_instance_by_name_or_id(args[0])
3911
+ return 1 if instance.nil?
3912
+ payload = {}
3913
+ if options[:payload]
3914
+ payload = options[:payload]
3915
+ payload.deep_merge!(parse_passed_options(options))
3916
+ else
3917
+ payload.deep_merge!(parse_passed_options(options))
3918
+ if payload['templateName'].nil?
3919
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'templateName', 'type' => 'text', 'fieldLabel' => 'Image Name', 'description' => 'Choose a name for the new image template. Default is the server name + timestamp'}], options[:options])
3920
+ if v_prompt['templateName'].to_s != ''
3921
+ payload['templateName'] = v_prompt['templateName']
3922
+ end
3923
+ end
3924
+ end
3925
+ @instances_interface.setopts(options)
3926
+ if options[:dry_run]
3927
+ print_dry_run @instances_interface.dry.clone_image(instance['id'], payload)
3928
+ return
3929
+ end
3930
+ json_response = @instances_interface.clone_image(instance['id'], payload)
3931
+ render_response(json_response, options) do
3932
+ print_green_success "Clone Image initiated."
3933
+ end
3934
+ return 0, nil
3935
+ end
3936
+
3937
+ def lock(args)
3938
+ options = {}
3939
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
3940
+ opts.banner = subcommand_usage("[instance]")
3941
+ build_standard_update_options(opts, options)
3942
+ opts.footer = <<-EOT
3943
+ Lock an instance
3944
+ [instance] is required. This is the name or id of an instance
3945
+ EOT
3946
+ end
3947
+ optparse.parse!(args)
3948
+ verify_args!(args:args, optparse:optparse, count:1)
3949
+ connect(options)
3950
+ instance = find_instance_by_name_or_id(args[0])
3951
+ return 1 if instance.nil?
3952
+ payload = {}
3953
+ if options[:payload]
3954
+ payload = options[:payload]
3955
+ payload.deep_merge!(parse_passed_options(options))
3956
+ else
3957
+ payload.deep_merge!(parse_passed_options(options))
3958
+ end
3959
+ @instances_interface.setopts(options)
3960
+ if options[:dry_run]
3961
+ print_dry_run @instances_interface.dry.lock(instance['id'], payload)
3962
+ return
3963
+ end
3964
+ json_response = @instances_interface.lock(instance['id'], payload)
3965
+ render_response(json_response, options) do
3966
+ print_green_success "Locked instance #{instance['name']}"
3967
+ end
3968
+ return 0, nil
3969
+ end
3970
+
3971
+ def unlock(args)
3972
+ options = {}
3973
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
3974
+ opts.banner = subcommand_usage("[instance]")
3975
+ build_standard_update_options(opts, options)
3976
+ opts.footer = <<-EOT
3977
+ Unlock an instance
3978
+ [instance] is required. This is the name or id of an instance
3979
+ EOT
3980
+ end
3981
+ optparse.parse!(args)
3982
+ verify_args!(args:args, optparse:optparse, count:1)
3983
+ connect(options)
3984
+ instance = find_instance_by_name_or_id(args[0])
3985
+ return 1 if instance.nil?
3986
+ payload = {}
3987
+ if options[:payload]
3988
+ payload = options[:payload]
3989
+ payload.deep_merge!(parse_passed_options(options))
3990
+ else
3991
+ payload.deep_merge!(parse_passed_options(options))
3992
+ end
3993
+ @instances_interface.setopts(options)
3994
+ if options[:dry_run]
3995
+ print_dry_run @instances_interface.dry.unlock(instance['id'], payload)
3996
+ return
3997
+ end
3998
+ json_response = @instances_interface.unlock(instance['id'], payload)
3999
+ render_response(json_response, options) do
4000
+ print_green_success "Unlocked instance #{instance['name']}"
4001
+ end
4002
+ return 0, nil
4003
+ end
4004
+
3860
4005
  private
3861
4006
 
3862
4007
  def find_zone_by_name_or_id(group_id, val)
@@ -8,7 +8,7 @@ class Morpheus::Cli::InvoicesCommand
8
8
 
9
9
  set_command_name :'invoices'
10
10
 
11
- register_subcommands :list, :get, :refresh,
11
+ register_subcommands :list, :get, :update, :refresh,
12
12
  :list_line_items, :get_line_item
13
13
 
14
14
  def connect(opts)
@@ -33,7 +33,6 @@ class Morpheus::Cli::InvoicesCommand
33
33
  options[:show_estimates] = true
34
34
  # options[:show_costs] = true
35
35
  options[:show_prices] = true
36
- # options[:show_raw_data] = true
37
36
  end
38
37
  opts.on('--dates', "Display Ref Start, Ref End, etc.") do |val|
39
38
  options[:show_dates] = true
@@ -117,9 +116,6 @@ class Morpheus::Cli::InvoicesCommand
117
116
  options[:tags][k] << (v || '')
118
117
  end
119
118
  end
120
- opts.on('--raw-data', '--raw-data', "Display Raw Data, the cost data from the cloud provider's API.") do |val|
121
- options[:show_raw_data] = true
122
- end
123
119
  opts.on('--totals', "View total costs and prices for all the invoices found.") do |val|
124
120
  params['includeTotals'] = true
125
121
  options[:show_invoice_totals] = true
@@ -173,7 +169,6 @@ class Morpheus::Cli::InvoicesCommand
173
169
  return 1, "projects not found for #{options[:projects]}" if project_ids.nil?
174
170
  params['projectId'] = project_ids
175
171
  end
176
- params['rawData'] = true if options[:show_raw_data]
177
172
  params['refId'] = ref_ids unless ref_ids.empty?
178
173
  if options[:tags] && !options[:tags].empty?
179
174
  options[:tags].each do |k,v|
@@ -271,8 +266,8 @@ class Morpheus::Cli::InvoicesCommand
271
266
  columns += [
272
267
  {"ESTIMATE" => lambda {|it| format_boolean(it['estimate']) } },
273
268
  {"ACTIVE" => lambda {|it| format_boolean(it['active']) } },
274
- {"ITEMS" => lambda {|it| it['lineItems'].size rescue '' } },
275
- {"TAGS" => lambda {|it| it['metadata'] ? it['metadata'].collect {|m| "#{m['name']}: #{m['value']}" }.join(', ') : '' } },
269
+ {"ITEMS" => lambda {|it| (it['lineItemCount'] ? it['lineItemCount'] : it['lineItems'].size) rescue '' } },
270
+ {"TAGS" => lambda {|it| (it['metadata'] || it['tags']) ? (it['metadata'] || it['tags']).collect {|m| "#{m['name']}: #{m['value']}" }.join(', ') : '' } },
276
271
  ]
277
272
  if show_projects
278
273
  columns += [
@@ -291,9 +286,6 @@ class Morpheus::Cli::InvoicesCommand
291
286
  {"CREATED" => lambda {|it| format_local_dt(it['dateCreated']) } },
292
287
  {"UPDATED" => lambda {|it| format_local_dt(it['lastUpdated']) } },
293
288
  ]
294
- if options[:show_raw_data]
295
- columns += [{"RAW DATA" => lambda {|it| truncate_string(it['rawData'].to_s, 10) } }]
296
- end
297
289
  unless options[:totals_only]
298
290
  print as_pretty_table(invoices, columns, options)
299
291
  print_results_pagination(json_response, {:label => "invoice", :n_label => "invoices"})
@@ -361,7 +353,6 @@ class Morpheus::Cli::InvoicesCommand
361
353
  options[:show_estimates] = true
362
354
  # options[:show_costs] = true
363
355
  options[:show_prices] = true
364
- # options[:show_raw_data] = true
365
356
  options[:max_line_items] = 10000
366
357
  end
367
358
  opts.on('--prices', '--prices', "Display prices: Total, Compute, Storage, Network, Extra" ) do
@@ -370,13 +361,6 @@ class Morpheus::Cli::InvoicesCommand
370
361
  opts.on('--estimates', '--estimates', "Display all estimated costs, from usage info: Compute, Storage, Network, Extra" ) do
371
362
  options[:show_estimates] = true
372
363
  end
373
- opts.on('--raw-data', '--raw-data', "Display Raw Data, the cost data from the cloud provider's API.") do |val|
374
- options[:show_raw_data] = true
375
- end
376
- opts.on('--pretty-raw-data', '--raw-data', "Display Raw Data that is a bit more pretty") do |val|
377
- options[:show_raw_data] = true
378
- options[:pretty_json] = true
379
- end
380
364
  opts.on('--no-line-items', '--no-line-items', "Do not display line items.") do |val|
381
365
  options[:hide_line_items] = true
382
366
  end
@@ -401,9 +385,6 @@ EOT
401
385
 
402
386
  def _get(id, options)
403
387
  params = {}
404
- if options[:show_raw_data]
405
- params['rawData'] = true
406
- end
407
388
  begin
408
389
  @invoices_interface.setopts(options)
409
390
  if options[:dry_run]
@@ -412,6 +393,9 @@ EOT
412
393
  end
413
394
  json_response = @invoices_interface.get(id, params)
414
395
  invoice = json_response['invoice']
396
+ if options[:hide_line_items]
397
+ json_response['invoice'].delete('lineItems') rescue nil
398
+ end
415
399
  render_result = render_with_format(json_response, options, 'invoice')
416
400
  return 0 if render_result
417
401
 
@@ -437,8 +421,8 @@ EOT
437
421
  "End" => lambda {|it| format_date(it['endDate']) },
438
422
  "Ref Start" => lambda {|it| format_dt(it['refStart']) },
439
423
  "Ref End" => lambda {|it| format_dt(it['refEnd']) },
440
- "Items" => lambda {|it| it['lineItems'].size rescue '' },
441
- "Tags" => lambda {|it| it['metadata'] ? it['metadata'].collect {|m| "#{m['name']}: #{m['value']}" }.join(', ') : '' },
424
+ "Items" => lambda {|it| (it['lineItemCount'] ? it['lineItemCount'] : it['lineItems'].size) rescue '' },
425
+ "Tags" => lambda {|it| (it['metadata'] || it['tags']) ? (it['metadata'] || it['tags']).collect {|m| "#{m['name']}: #{m['value']}" }.join(', ') : '' },
442
426
  "Project ID" => lambda {|it| it['project'] ? it['project']['id'] : '' },
443
427
  "Project Name" => lambda {|it| it['project'] ? it['project']['name'] : '' },
444
428
  "Project Tags" => lambda {|it| it['project'] ? format_metadata(it['project']['tags']) : '' },
@@ -454,7 +438,8 @@ EOT
454
438
  description_cols.delete("Project Name")
455
439
  description_cols.delete("Project Tags")
456
440
  end
457
- if invoice['metadata'].nil? || invoice['metadata'].empty?
441
+ tags = (invoice['metadata'] || invoice['tags'])
442
+ if tags.nil? || tags.empty?
458
443
  description_cols.delete("Tags")
459
444
  end
460
445
  if !['ComputeServer','Instance','Container'].include?(invoice['refType'])
@@ -522,9 +507,6 @@ EOT
522
507
  {"CREATED" => lambda {|it| format_local_dt(it['dateCreated']) } },
523
508
  {"UPDATED" => lambda {|it| format_local_dt(it['lastUpdated']) } }
524
509
  ]
525
- if options[:show_raw_data]
526
- line_items_columns += [{"RAW DATA" => lambda {|it| truncate_string(it['rawData'].to_s, 10) } }]
527
- end
528
510
  print_h2 "Line Items"
529
511
  #max_line_items = options[:max_line_items] ? options[:max_line_items].to_i : 5
530
512
  paged_line_items = line_items #.first(max_line_items)
@@ -577,10 +559,7 @@ EOT
577
559
  end
578
560
  print as_pretty_table(cost_rows, cost_columns, options)
579
561
 
580
- if options[:show_raw_data]
581
- print_h2 "Raw Data"
582
- puts as_json(invoice['rawData'], {pretty_json:false}.merge(options))
583
- end
562
+
584
563
 
585
564
  print reset,"\n"
586
565
  return 0
@@ -590,6 +569,68 @@ EOT
590
569
  end
591
570
  end
592
571
 
572
+ def update(args)
573
+ options = {}
574
+ params = {}
575
+ payload = {}
576
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
577
+ opts.banner = subcommand_usage("[invoice] [options]")
578
+ opts.on('--tags LIST', String, "Tags in the format 'name:value, name:value'. This will add and remove tags.") do |val|
579
+ options[:tags] = val
580
+ end
581
+ opts.on('--add-tags TAGS', String, "Add Tags in the format 'name:value, name:value'. This will only add/update tags.") do |val|
582
+ options[:add_tags] = val
583
+ end
584
+ opts.on('--remove-tags TAGS', String, "Remove Tags in the format 'name, name:value'. This removes tags, the :value component is optional and must match if passed.") do |val|
585
+ options[:remove_tags] = val
586
+ end
587
+ build_standard_update_options(opts, options)
588
+ opts.footer = <<-EOT
589
+ Update an invoice.
590
+ [invoice] is required. This is the id of an invoice.
591
+ EOT
592
+ end
593
+ optparse.parse!(args)
594
+ verify_args!(args:args, optparse:optparse, count:1)
595
+ connect(options)
596
+ json_response = @invoices_interface.get(args[0])
597
+ invoice = json_response['invoice']
598
+
599
+ invoice_payload = parse_passed_options(options)
600
+ if options[:tags]
601
+ invoice_payload['tags'] = parse_metadata(options[:tags])
602
+ end
603
+ if options[:add_tags]
604
+ invoice_payload['addTags'] = parse_metadata(options[:add_tags])
605
+ end
606
+ if options[:remove_tags]
607
+ invoice_payload['removeTags'] = parse_metadata(options[:remove_tags])
608
+ end
609
+
610
+ payload = {}
611
+ if options[:payload]
612
+ payload = options[:payload]
613
+ payload.deep_merge!({'invoice' => invoice_payload})
614
+ else
615
+ payload.deep_merge!({'invoice' => invoice_payload})
616
+ if invoice_payload.empty?
617
+ raise_command_error "Specify at least one option to update.\n#{optparse}"
618
+ end
619
+ end
620
+ @invoices_interface.setopts(options)
621
+ if options[:dry_run]
622
+ print_dry_run @invoices_interface.dry.update(invoice['id'], payload)
623
+ return
624
+ end
625
+ json_response = @invoices_interface.update(invoice['id'], payload)
626
+ invoice = json_response['invoice']
627
+ render_response(json_response, options, 'invoice') do
628
+ print_green_success "Updated invoice #{invoice['id']}"
629
+ return _get(invoice["id"], options)
630
+ end
631
+ return 0, nil
632
+ end
633
+
593
634
  def refresh(args)
594
635
  options = {}
595
636
  params = {}
@@ -677,7 +718,6 @@ EOT
677
718
  options[:show_actual_costs] = true
678
719
  options[:show_costs] = true
679
720
  options[:show_prices] = true
680
- # options[:show_raw_data] = true
681
721
  end
682
722
  # opts.on('--actuals', '--actuals', "Display all actual costs: Compute, Storage, Network, Extra" ) do
683
723
  # options[:show_actual_costs] = true
@@ -766,9 +806,6 @@ EOT
766
806
  options[:tags][k] << (v || '')
767
807
  end
768
808
  end
769
- opts.on('--raw-data', '--raw-data', "Display Raw Data, the cost data from the cloud provider's API.") do |val|
770
- options[:show_raw_data] = true
771
- end
772
809
  opts.on('--totals', "View total costs and prices for all the invoices found.") do |val|
773
810
  params['includeTotals'] = true
774
811
  options[:show_invoice_totals] = true
@@ -823,7 +860,6 @@ EOT
823
860
  return 1, "projects not found for #{options[:projects]}" if project_ids.nil?
824
861
  params['projectId'] = project_ids
825
862
  end
826
- params['rawData'] = true if options[:show_raw_data]
827
863
  params['refId'] = ref_ids unless ref_ids.empty?
828
864
  if options[:tags] && !options[:tags].empty?
829
865
  options[:tags].each do |k,v|
@@ -881,9 +917,6 @@ EOT
881
917
  "UPDATED" => lambda {|it| format_local_dt(it['lastUpdated']) }
882
918
  ]
883
919
 
884
- if options[:show_raw_data]
885
- columns += [{"RAW DATA" => lambda {|it| truncate_string(it['rawData'].to_s, 10) } }]
886
- end
887
920
  # if options[:show_invoice_totals]
888
921
  # line_item_totals = json_response['lineItemTotals']
889
922
  # if line_item_totals
@@ -932,13 +965,6 @@ EOT
932
965
  options = {}
933
966
  optparse = Morpheus::Cli::OptionParser.new do |opts|
934
967
  opts.banner = subcommand_usage("[id]")
935
- opts.on('--raw-data', '--raw-data', "Display Raw Data, the cost data from the cloud provider's API.") do |val|
936
- options[:show_raw_data] = true
937
- end
938
- opts.on('--pretty-raw-data', '--raw-data', "Display Raw Data that is a bit more pretty") do |val|
939
- options[:show_raw_data] = true
940
- options[:pretty_json] = true
941
- end
942
968
  opts.on('--sigdig DIGITS', "Significant digits when rounding cost values for display as currency. Default is 2. eg. $3.50") do |val|
943
969
  options[:sigdig] = val.to_i
944
970
  end
@@ -960,9 +986,6 @@ EOT
960
986
 
961
987
  def _get_line_item(id, options)
962
988
  params = {}
963
- if options[:show_raw_data]
964
- params['rawData'] = true
965
- end
966
989
  @invoice_line_items_interface.setopts(options)
967
990
  if options[:dry_run]
968
991
  print_dry_run @invoice_line_items_interface.dry.get(id, params)
@@ -1000,11 +1023,6 @@ EOT
1000
1023
  }
1001
1024
  print_description_list(description_cols, line_item, options)
1002
1025
 
1003
- if options[:show_raw_data]
1004
- print_h2 "Raw Data"
1005
- puts as_json(line_item['rawData'], {pretty_json:false}.merge(options))
1006
- end
1007
-
1008
1026
  print reset,"\n"
1009
1027
  end
1010
1028
  return 0, nil