morpheus-cli 8.0.7 → 8.0.9

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.
@@ -28,6 +28,7 @@ class Morpheus::Cli::Clusters
28
28
  register_subcommands :refresh
29
29
  register_subcommands :list_replicasets, :list_daemonsets, :list_endpoints, :list_ingresses, :list_policies, :list_volumes, :list_volume_claims, :list_config_maps, :list_secrets
30
30
  register_subcommands :get_pod, :get_deployment, :get_replicaset, :get_daemonset, :get_endpoint, :get_ingress, :get_policy, :get_volume_claim, :get_volume, :get_config_map, :get_secret, :get_stateful_set, :get_job, :get_service
31
+ register_subcommands :list_affinity_groups, :get_affinity_group, :update_affinity_group, :add_affinity_group, :remove_affinity_group
31
32
 
32
33
  def connect(opts)
33
34
  @api_client = establish_remote_appliance_connection(opts)
@@ -1379,7 +1380,12 @@ class Morpheus::Cli::Clusters
1379
1380
  (['provisionType.vmware.host', 'provisionType.scvmm.host'].include?(type['code']) && cloud['config']['hideHostSelection'] == 'on') || # should this be truthy?
1380
1381
  (type['fieldContext'] == 'instance.networkDomain' && type['fieldName'] == 'id')
1381
1382
  } rescue [])
1382
-
1383
+ # strip server context
1384
+ option_type_list.each do |option_type|
1385
+ if option_type['fieldContext'] == 'server' || option_type['fieldContext'] == 'domain'
1386
+ option_type['fieldContext'] = nil
1387
+ end
1388
+ end
1383
1389
  # remove metadata option_type , prompt manually for that field 'tags' instead of 'metadata'
1384
1390
  #metadata_option_type = option_type_list.find {|type| type['fieldName'] == 'metadata' }
1385
1391
  metadata_option_type = cluster_type['optionTypes'].find {|type| type['fieldName'] == 'metadata' }
@@ -3269,6 +3275,337 @@ class Morpheus::Cli::Clusters
3269
3275
  end
3270
3276
  end
3271
3277
 
3278
+ def list_affinity_groups(args)
3279
+ options = {}
3280
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
3281
+ opts.banner = subcommand_usage( "[cluster]")
3282
+ build_standard_list_options(opts, options)
3283
+ opts.footer = "List affinity groups for a cluster.\n" +
3284
+ "[cluster] is required. This is the name or id of an existing cluster."
3285
+ end
3286
+
3287
+ optparse.parse!(args)
3288
+ verify_args!(args:args, optparse:optparse, count:1)
3289
+ connect(options)
3290
+
3291
+ cluster = find_cluster_by_name_or_id(args[0])
3292
+ return 1 if cluster.nil?
3293
+
3294
+ params = {}
3295
+ params.merge!(parse_list_options(options))
3296
+ @clusters_interface.setopts(options)
3297
+ if options[:dry_run]
3298
+ print_dry_run @clusters_interface.dry.list_affinity_groups(cluster['id'], params)
3299
+ return
3300
+ end
3301
+ json_response = @clusters_interface.list_affinity_groups(cluster['id'], params)
3302
+ render_response(json_response, options, 'affinityGroups') do
3303
+ affinity_groups = json_response['affinityGroups']
3304
+ print_h1 "Morpheus Cluster Affinity Groups: #{cluster['name']}", parse_list_subtitles(options), options
3305
+ if affinity_groups.empty?
3306
+ print cyan,"No affinity groups found.",reset,"\n"
3307
+ else
3308
+ columns = {
3309
+ "ID" => 'id',
3310
+ "Name" => 'name',
3311
+ "Type" => lambda {|it| format_affinity_type(it['affinityType']) },
3312
+ "Resource Pool" => lambda {|it| it['pool'] ? (it['pool']['name'] || it['pool']['id']) : '' },
3313
+ "Visibility" => lambda {|it| it['visibility'].to_s.capitalize },
3314
+ "Servers" => lambda {|it| it['servers'].size() },
3315
+ # "Source" => lambda {|it| it['source'] },
3316
+ }.upcase_keys!
3317
+ print as_pretty_table(affinity_groups, columns, options)
3318
+ print_results_pagination(json_response)
3319
+ end
3320
+ print reset,"\n"
3321
+ end
3322
+ return 0, nil
3323
+ end
3324
+
3325
+ def get_affinity_group(args)
3326
+ options = {}
3327
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
3328
+ opts.banner = subcommand_usage( "[cluster] [affinity group]")
3329
+ build_standard_get_options(opts, options)
3330
+ opts.footer = "Get details about a cluster affinity group.\n" +
3331
+ "[cluster] is required. This is the name or id of an existing cluster.\n" +
3332
+ "[affinity group] is required. This is the name or id of an existing affinity group."
3333
+ end
3334
+ optparse.parse!(args)
3335
+ verify_args!(args:args, optparse:optparse, count:2)
3336
+ connect(options)
3337
+
3338
+ cluster = find_cluster_by_name_or_id(args[0])
3339
+ return 1 if cluster.nil?
3340
+
3341
+ # this finds the affinity group in the cluster api response, then fetches it by ID
3342
+ affinity_group = find_cluster_affinity_group_by_name_or_id(cluster['id'], args[1])
3343
+ if affinity_group.nil?
3344
+ print_red_alert "Affinity Group not found for '#{args[1]}'"
3345
+ exit 1
3346
+ end
3347
+
3348
+ params = {}
3349
+ params.merge!(parse_query_options(options))
3350
+ @clusters_interface.setopts(options)
3351
+ if options[:dry_run]
3352
+ print_dry_run @clusters_interface.dry.get_affinity_group(cluster['id'], affinity_group['id'], params)
3353
+ return
3354
+ end
3355
+ json_response = @clusters_interface.get_affinity_group(cluster['id'], affinity_group['id'], params)
3356
+ render_response(json_response, options, 'affinityGroup') do
3357
+ affinity_group = json_response['affinityGroup']
3358
+ print_h1 "Affinity Group Details", [], options
3359
+ columns = {
3360
+ "ID" => 'id',
3361
+ "Name" => 'name',
3362
+ "Type" => lambda {|it| format_affinity_type(it['affinityType']) },
3363
+ "Resource Pool" => lambda {|it| it['pool'] ? (it['pool']['name'] || it['pool']['id']) : '' },
3364
+ "Visibility" => lambda {|it| it['visibility'].to_s.capitalize },
3365
+ "Servers" => lambda {|it| it['servers'].size() },
3366
+ "Source" => lambda {|it| it['source'] },
3367
+ "Active" => lambda {|it| format_boolean(it['active']) },
3368
+ }
3369
+ print_description_list(columns, affinity_group)
3370
+ if affinity_group['servers'].size > 0
3371
+ print_h2 "Servers", options
3372
+ print as_pretty_table(affinity_group['servers'], [:id, :name], options)
3373
+ end
3374
+ print reset,"\n"
3375
+ end
3376
+ return 0, nil
3377
+ end
3378
+
3379
+ def add_affinity_group(args)
3380
+ options = {}
3381
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
3382
+ opts.banner = subcommand_usage( "[cluster] [name] [options]")
3383
+ build_option_type_options(opts, options, add_affinity_group_option_types)
3384
+ # opts.on('--refresh [SECONDS]', String, "Refresh until execution is complete. Default interval is #{default_refresh_interval} seconds.") do |val|
3385
+ # options[:refresh_interval] = val.to_s.empty? ? default_refresh_interval : val.to_f
3386
+ # end
3387
+ # opts.on(nil, '--no-refresh', "Do not refresh" ) do
3388
+ # options[:no_refresh] = true
3389
+ # end
3390
+ build_standard_add_options(opts, options)
3391
+ opts.footer = "Add affinity group to a cluster.\n" +
3392
+ "[cluster] is required. This is the name or id of an existing cluster.\n" +
3393
+ "[name] is required. This is the name of the new affinity group."
3394
+ end
3395
+
3396
+ optparse.parse!(args)
3397
+ verify_args!(args:args, optparse:optparse, min:1, max:2)
3398
+ connect(options)
3399
+
3400
+ begin
3401
+ cluster = find_cluster_by_name_or_id(args[0])
3402
+ return 1 if cluster.nil?
3403
+ if args[1]
3404
+ options[:options]['name'] = args[1]
3405
+ end
3406
+ if options[:payload]
3407
+ payload = options[:payload]
3408
+ # support -O OPTION switch on top of --payload
3409
+ if options[:options]
3410
+ payload ||= {}
3411
+ payload.deep_merge!(options[:options].reject {|k,v| k.is_a?(Symbol) })
3412
+ end
3413
+ else
3414
+ options[:params] ||= {}
3415
+ options[:params].merge!({:serverGroupId => cluster['id']})
3416
+ option_types = add_affinity_group_option_types
3417
+ affinity_group = Morpheus::Cli::OptionTypes.prompt(option_types, options[:options], @api_client, options[:params])
3418
+
3419
+ # affinity_group_type = find_affinity_group_type_by_code(affinity_group['affinityGroupType'])
3420
+ # affinity_group['affinityGroupType'] = {id:affinity_group_type['id']}
3421
+
3422
+ # # affinity_group type options
3423
+ # unless affinity_group_type['optionTypes'].empty?
3424
+ # affinity_group.merge!(Morpheus::Cli::OptionTypes.prompt(affinity_group_type['optionTypes'], options[:options].deep_merge({:context_map => {'domain' => ''}, :checkbox_as_boolean => true}), @api_client, options[:params]))
3425
+ # end
3426
+
3427
+ # perms
3428
+ perms = prompt_permissions(options.merge({:for_affinity_group => true}), ['groupDefaults'])
3429
+
3430
+ affinity_group['resourcePermissions'] = perms['resourcePermissions'] unless perms['resourcePermissions'].nil?
3431
+ affinity_group['tenants'] = perms['tenantPermissions'] unless perms['tenantPermissions'].nil?
3432
+ affinity_group['visibility'] = perms['resourcePool']['visibility'] if !perms['resourcePool'].nil? && !perms['resourcePool']['visibility'].nil?
3433
+
3434
+ payload = {'affinityGroup' => affinity_group}
3435
+ end
3436
+
3437
+ @clusters_interface.setopts(options)
3438
+ if options[:dry_run]
3439
+ print_dry_run @clusters_interface.dry.create_affinity_group(cluster['id'], payload)
3440
+ return
3441
+ end
3442
+ json_response = @clusters_interface.create_affinity_group(cluster['id'], payload)
3443
+ if options[:json]
3444
+ puts as_json(json_response)
3445
+ else
3446
+ if json_response['success']
3447
+ if json_response['msg'] == nil
3448
+ print_green_success "Adding affinity group to cluster #{cluster['name']}"
3449
+ else
3450
+ print_green_success json_response['msg']
3451
+ end
3452
+ execution_id = json_response['executionId']
3453
+ if !options[:no_refresh] && execution_id
3454
+ wait_for_execution_request(json_response['executionId'], options.merge({waiting_status:['new', 'pending', 'executing']}))
3455
+ end
3456
+ else
3457
+ print_red_alert "Failed to create cluster affinity group #{json_response['msg']}"
3458
+ end
3459
+ end
3460
+ return 0
3461
+ rescue RestClient::Exception => e
3462
+ print_rest_exception(e, options)
3463
+ exit 1
3464
+ end
3465
+ end
3466
+
3467
+ def update_affinity_group(args)
3468
+ options = {}
3469
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
3470
+ opts.banner = subcommand_usage( "[cluster] [affinity group] [options]")
3471
+ opts.on('--active [on|off]', String, "Enable affinity group") do |val|
3472
+ options[:active] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == ''
3473
+ end
3474
+ # add_perms_options(opts, options, ['groupDefaults'])
3475
+ build_standard_update_options(opts, options)
3476
+ opts.footer = "Update a cluster affinity group.\n" +
3477
+ "[cluster] is required. This is the name or id of an existing cluster.\n" +
3478
+ "[affinity group] is required. This is the name or id of an existing affinity group."
3479
+ end
3480
+
3481
+ optparse.parse!(args)
3482
+ if args.count != 2
3483
+ raise_command_error "wrong number of arguments, expected 2 and got (#{args.count}) #{args}\n#{optparse}"
3484
+ end
3485
+ connect(options)
3486
+
3487
+ begin
3488
+ cluster = find_cluster_by_name_or_id(args[0])
3489
+ return 1 if cluster.nil?
3490
+ affinity_group = find_cluster_affinity_group_by_name_or_id(cluster['id'], args[1])
3491
+ if affinity_group.nil?
3492
+ print_red_alert "Affinity Group not found by '#{args[1]}'"
3493
+ exit 1
3494
+ end
3495
+ payload = nil
3496
+ if options[:payload]
3497
+ payload = options[:payload]
3498
+ # support -O OPTION switch on top of everything
3499
+ if options[:options]
3500
+ payload.deep_merge!({'affinityGroup' => options[:options].reject {|k,v| k.is_a?(Symbol) }})
3501
+ end
3502
+ else
3503
+ payload = {'affinityGroup' => {}}
3504
+ payload['affinityGroup']['active'] = options[:active].nil? ? (Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'active', 'fieldLabel' => 'Active', 'type' => 'checkbox', 'description' => 'Active', 'defaultValue' => true}], options[:options], @api_client))['active'] == 'on' : options[:active]
3505
+
3506
+ perms = prompt_permissions(options.merge({:available_plans => namespace_service_plans}), affinity_group['owner']['id'] == current_user['accountId'] ? ['plans', 'groupDefaults'] : ['plans', 'groupDefaults', 'visibility', 'tenants'])
3507
+ perms_payload = {}
3508
+ perms_payload['resourcePermissions'] = perms['resourcePermissions'] if !perms['resourcePermissions'].nil?
3509
+ perms_payload['tenantPermissions'] = perms['tenantPermissions'] if !perms['tenantPermissions'].nil?
3510
+
3511
+ payload['affinityGroup']['permissions'] = perms_payload
3512
+ payload['affinityGroup']['visibility'] = perms['resourcePool']['visibility'] if !perms['resourcePool'].nil? && !perms['resourcePool']['visibility'].nil?
3513
+
3514
+ # support -O OPTION switch on top of everything
3515
+ if options[:options]
3516
+ payload.deep_merge!({'affinityGroup' => options[:options].reject {|k,v| k.is_a?(Symbol) }})
3517
+ end
3518
+
3519
+ if payload['affinityGroup'].nil? || payload['affinityGroup'].empty?
3520
+ raise_command_error "Specify at least one option to update.\n#{optparse}"
3521
+ end
3522
+ end
3523
+
3524
+ @clusters_interface.setopts(options)
3525
+ if options[:dry_run]
3526
+ print_dry_run @clusters_interface.dry.update_affinity_group(cluster['id'], affinity_group['id'], payload)
3527
+ return
3528
+ end
3529
+ json_response = @clusters_interface.update_affinity_group(cluster['id'], affinity_group['id'], payload)
3530
+ if options[:json]
3531
+ puts as_json(json_response)
3532
+ elsif !options[:quiet]
3533
+ affinity_group = json_response['affinityGroup']
3534
+ print_green_success "Updated affinity group #{affinity_group['name']}"
3535
+ #get_args = [cluster["id"], affinity_group["id"]] + (options[:remote] ? ["-r",options[:remote]] : [])
3536
+ #get_namespace(get_args)
3537
+ end
3538
+ return 0
3539
+ rescue RestClient::Exception => e
3540
+ print_rest_exception(e, options)
3541
+ exit 1
3542
+ end
3543
+ end
3544
+
3545
+ def remove_affinity_group(args)
3546
+ default_refresh_interval = 5
3547
+ params = {}
3548
+ options = {}
3549
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
3550
+ opts.banner = subcommand_usage("[cluster] [affinity group]")
3551
+ # opts.on( '-f', '--force', "Force Delete" ) do
3552
+ # params[:force] = 'on'
3553
+ # end
3554
+ # opts.on('--refresh [SECONDS]', String, "Refresh until execution is complete. Default interval is #{default_refresh_interval} seconds.") do |val|
3555
+ # options[:refresh_interval] = val.to_s.empty? ? default_refresh_interval : val.to_f
3556
+ # end
3557
+ # opts.on(nil, '--no-refresh', "Do not refresh" ) do
3558
+ # options[:no_refresh] = true
3559
+ # end
3560
+ build_standard_remove_options(opts, options)
3561
+ opts.footer = "Delete an affinity group from a cluster.\n" +
3562
+ "[cluster] is required. This is the name or id of an existing cluster.\n" +
3563
+ "[affinity group] is required. This is the name or id of an existing affinity group."
3564
+ end
3565
+ optparse.parse!(args)
3566
+ verify_args!(args:args, optparse:optparse, count:2)
3567
+ connect(options)
3568
+ params.merge!(parse_query_options(options))
3569
+
3570
+ cluster = find_cluster_by_name_or_id(args[0])
3571
+ return 1 if cluster.nil?
3572
+
3573
+ affinity_group_id = args[1]
3574
+ if affinity_group_id.empty?
3575
+ raise_command_error "missing required worker parameter"
3576
+ end
3577
+
3578
+ affinity_group = find_cluster_affinity_group_by_name_or_id(cluster['id'], affinity_group_id)
3579
+ if affinity_group.nil?
3580
+ print_red_alert "Affinity Group not found for '#{affinity_group_id}'"
3581
+ return 1
3582
+ end
3583
+ unless options[:yes] || ::Morpheus::Cli::OptionTypes::confirm("Are you sure you would like to remove the cluster affinity group '#{affinity_group['name'] || affinity_group['id']}'?", options)
3584
+ return 9, "aborted command"
3585
+ end
3586
+
3587
+ @clusters_interface.setopts(options)
3588
+ if options[:dry_run]
3589
+ print_dry_run @clusters_interface.dry.destroy_affinity_group(cluster['id'], affinity_group['id'], params)
3590
+ return
3591
+ end
3592
+ json_response = @clusters_interface.destroy_affinity_group(cluster['id'], affinity_group['id'], params)
3593
+ if options[:json]
3594
+ puts as_json(json_response)
3595
+ else
3596
+ if json_response['success']
3597
+ print_green_success "Removed affinity group #{affinity_group['name']}"
3598
+ execution_id = json_response['executionId']
3599
+ if !options[:no_refresh] && execution_id
3600
+ wait_for_execution_request(execution_id, options.merge({waiting_status:['new', 'pending', 'executing']}))
3601
+ end
3602
+ else
3603
+ print_red_alert "Failed to remove cluster affinity_group #{json_response['msg']}"
3604
+ end
3605
+ end
3606
+ return 0, nil
3607
+ end
3608
+
3272
3609
  def api_config(args)
3273
3610
  options = {}
3274
3611
  optparse = Morpheus::Cli::OptionParser.new do |opts|
@@ -4866,4 +5203,27 @@ class Morpheus::Cli::Clusters
4866
5203
  connect(options)
4867
5204
  _list_container_groups(args, options, resource_type)
4868
5205
  end
5206
+
5207
+ def find_cluster_affinity_group_by_name_or_id(cluster_id, val)
5208
+ if val.to_s =~ /\A\d{1,}\Z/
5209
+ @clusters_interface.get_affinity_group(cluster_id, val)['affinityGroup'] rescue nil
5210
+ else
5211
+ @clusters_interface.list_affinity_groups(cluster_id, {name: val})['affinityGroups'][0]
5212
+ end
5213
+ end
5214
+
5215
+ def add_affinity_group_option_types
5216
+ [
5217
+ {'fieldName' => 'name', 'fieldLabel' => 'Name', 'type' => 'text', 'required' => true},
5218
+ {'fieldName' => 'affinityType', 'fieldLabel' => 'Type', 'type' => 'select', 'selectOptions' => [{'name' => 'Keep Separate', 'value' => 'KEEP_SEPARATE'}, {'name' => 'Keep Together', 'value' => 'KEEP_TOGETHER'}], 'description' => 'Choose affinity type.', 'required' => true, 'defaultValue' => 'KEEP_SEPARATE'},
5219
+ {'fieldName' => 'active', 'fieldLabel' => 'Active', 'type' => 'checkbox', 'defaultValue' => true},
5220
+ # {'fieldName' => 'pool.id', 'fieldLabel' => 'Cluster', 'type' => 'select', 'optionSourceType' => 'vmware', 'optionSource' => 'vmwareZonePoolClusters', 'description' => 'Select cluster for the affinity group.', 'required' => true},
5221
+ {'fieldName' => 'servers', 'fieldLabel' => 'Server', 'type' => 'multiSelect', 'optionSource' => 'searchServers', 'description' => 'Select servers to be in the affinity group.'},
5222
+ ]
5223
+ end
5224
+
5225
+ def format_affinity_type(affinity_type)
5226
+ affinity_type == "KEEP_SEPARATE" ? "Keep Separate" : "Keep Together"
5227
+ end
5228
+
4869
5229
  end
@@ -3,6 +3,7 @@ require 'morpheus/cli/cli_command'
3
3
  class Morpheus::Cli::Hosts
4
4
  include Morpheus::Cli::CliCommand
5
5
  include Morpheus::Cli::AccountsHelper
6
+ include Morpheus::Cli::ProcessesHelper
6
7
  include Morpheus::Cli::ProvisioningHelper
7
8
  include Morpheus::Cli::LogsHelper
8
9
  set_command_name :hosts
@@ -13,7 +14,8 @@ class Morpheus::Cli::Hosts
13
14
  {:exec => :execution_request},
14
15
  :wiki, :update_wiki,
15
16
  :maintenance, :leave_maintenance, :placement,
16
- :list_devices, :assign_device, :detach_device, :attach_device
17
+ :list_devices, :assign_device, :detach_device, :attach_device,
18
+ :snapshot
17
19
  alias_subcommand :details, :get
18
20
  set_default_subcommand :list
19
21
 
@@ -538,8 +540,9 @@ class Morpheus::Cli::Hosts
538
540
  "Nodes" => lambda {|it| it['containers'] ? it['containers'].size : 0 },
539
541
  # "Status" => lambda {|it| format_server_status(it) },
540
542
  # "Power" => lambda {|it| format_server_power_state(it) },
541
- "Status" => lambda {|it| format_server_status_friendly(it) }, # combo
542
- "Managed" => lambda {|it| it['computeServerType'] ? it['computeServerType']['managed'] : ''}
543
+ "Managed" => lambda {|it| it['computeServerType'] ? it['computeServerType']['managed'] : ''},
544
+ "Instance" => lambda {|it| it['instance'] ? it['instance']['name'] : ''},
545
+ "Status" => lambda {|it| format_server_status_friendly(it) } # combo
543
546
  }
544
547
  server_columns.delete("Hostname") if server['hostname'].to_s.empty? || server['hostname'] == server['name']
545
548
  server_columns.delete("IP") if server['externalIp'].to_s.empty?
@@ -548,6 +551,7 @@ class Morpheus::Cli::Hosts
548
551
  server_columns.delete("Cost") if server['hourlyCost'].to_f == 0
549
552
  server_columns.delete("Price") if server['hourlyPrice'].to_f == 0 || server['hourlyPrice'] == server['hourlyCost']
550
553
  server_columns.delete("Labels") if server['labels'].nil? || server['labels'].empty?
554
+ server_columns.delete("Instance") if server['instance'].nil?
551
555
 
552
556
  print_description_list(server_columns, server)
553
557
 
@@ -1519,9 +1523,13 @@ EOT
1519
1523
  else
1520
1524
  print_green_success json_response['msg']
1521
1525
  end
1522
- execution_id = json_response['executionId']
1523
- if !options[:no_refresh] && execution_id
1524
- wait_for_execution_request(json_response['executionId'], options.merge({waiting_status:['new', 'pending', 'executing']}))
1526
+ process_id = json_response['processId'] || (json_response['processIds'] ? json_response['processIds'][0] : nil)
1527
+ if process_id
1528
+ unless options[:no_refresh]
1529
+ process = wait_for_process_execution(process_id, options)
1530
+ end
1531
+ else
1532
+ # puts "No process returned"
1525
1533
  end
1526
1534
  else
1527
1535
  # never reached because unsuccessful requests raise an exception
@@ -2043,13 +2051,18 @@ EOT
2043
2051
  def snapshots(args)
2044
2052
  options = {}
2045
2053
  optparse = Morpheus::Cli::OptionParser.new do |opts|
2046
- opts.banner = subcommand_usage("[host]")
2054
+ opts.banner = subcommand_usage("[host] [snapshot]")
2047
2055
  # no pagination yet
2048
2056
  # build_standard_list_options(opts, options)
2049
- build_standard_get_options(opts, options)
2057
+ build_standard_list_options(opts, options, [:details])
2058
+ opts.footer = <<-EOT
2059
+ List snapshots for a host.
2060
+ [host] is required. This is the name or id of a host.
2061
+ [snapshot] is optional. This is the name or id of a snapshot.
2062
+ EOT
2050
2063
  end
2051
2064
  optparse.parse!(args)
2052
- verify_args!(args:args, optparse:optparse, count:1)
2065
+ verify_args!(args:args, optparse:optparse, min:1)
2053
2066
  connect(options)
2054
2067
  begin
2055
2068
  server = find_host_by_name_or_id(args[0])
@@ -2061,25 +2074,52 @@ EOT
2061
2074
  return
2062
2075
  end
2063
2076
  json_response = @servers_interface.snapshots(server['id'], params)
2064
- snapshots = json_response['snapshots']
2077
+ snapshots = json_response['snapshots']
2078
+ # [snapshots] is done with post api filtering by id or name or externalId
2079
+ if args[1]
2080
+ if args[1] =~ /\A\d{1,}\Z/
2081
+ snapshots = snapshots.select {|it| it['id'].to_s == args[1] }
2082
+ else
2083
+ # match beginning of name of externalId
2084
+ snapshots = snapshots.select {|it| it['name'].to_s.index(args[1]) == 0 || it['externalId'].to_s.index(args[1]) == 0 }
2085
+ end
2086
+ json_response['snapshots'] = snapshots # update response for -j filtering too
2087
+ end
2065
2088
  render_response(json_response, options, 'snapshots') do
2066
2089
  print_h1 "Snapshots: #{server['name']}", [], options
2067
2090
  if snapshots.empty?
2068
- print cyan,"No snapshots found",reset,"\n"
2091
+ if args[1]
2092
+ print cyan,"No snapshots found for '#{args[1]}'",reset,"\n"
2093
+ elsif
2094
+ print cyan,"No snapshots found",reset,"\n"
2095
+ end
2096
+ print reset, "\n"
2069
2097
  else
2070
- snapshot_column_definitions = {
2071
- "ID" => lambda {|it| it['id'] },
2072
- "Name" => lambda {|it| it['name'] },
2073
- "Description" => lambda {|it| it['description'] },
2074
- # "Type" => lambda {|it| it['snapshotType'] },
2075
- "Date Created" => lambda {|it| format_local_dt(it['snapshotCreated']) },
2076
- "Status" => lambda {|it| format_snapshot_status(it) }
2077
- }
2078
- print cyan
2079
- print as_pretty_table(snapshots, snapshot_column_definitions.upcase_keys!, options)
2080
- print_results_pagination({size: snapshots.size, total: snapshots.size})
2098
+ if options[:details]
2099
+ # this actually makes a request for each one here so don't go crazy...
2100
+ if snapshots.size > 3
2101
+ print cyan, "Showing first 3 snapshots. Use the ID to get more details.", reset, "\n"
2102
+ snapshots = snapshots.first(3)
2103
+ end
2104
+ snapshots.each do |snapshot|
2105
+ Morpheus::Cli::Snapshots.new.handle(["get", snapshot['id']] + (options[:remote] ? ["-r",options[:remote]] : []))
2106
+ end
2107
+ else
2108
+ # Snapshots List
2109
+ snapshot_column_definitions = {
2110
+ "ID" => lambda {|it| it['id'] },
2111
+ "Name" => lambda {|it| it['name'] },
2112
+ "Description" => lambda {|it| it['description'] },
2113
+ # "Type" => lambda {|it| it['snapshotType'] },
2114
+ "Date Created" => lambda {|it| format_local_dt(it['snapshotCreated']) },
2115
+ "Status" => lambda {|it| format_snapshot_status(it) }
2116
+ }
2117
+ print cyan
2118
+ print as_pretty_table(snapshots, snapshot_column_definitions.upcase_keys!, options)
2119
+ print_results_pagination({size: snapshots.size, total: snapshots.size})
2120
+ print reset, "\n"
2121
+ end
2081
2122
  end
2082
- print reset, "\n"
2083
2123
  end
2084
2124
  return 0
2085
2125
  rescue RestClient::Exception => e
@@ -2442,6 +2482,94 @@ EOT
2442
2482
  return 0, nil
2443
2483
  end
2444
2484
 
2485
+ def snapshot(args)
2486
+ options = {}
2487
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
2488
+ opts.banner = subcommand_usage("[host]")
2489
+ opts.on( '--name VALUE', String, "Snapshot Name. Default is \"{name}.{timestamp}\"" ) do |val|
2490
+ options[:options]['name'] = val
2491
+ end
2492
+ opts.on( '--description VALUE', String, "Snapshot Description." ) do |val|
2493
+ options[:options]['description'] = val
2494
+ end
2495
+ opts.on('--memory-snapshot [on|off]', String, "Memory Snapshot? Whether to include the memory state in the snapshot.") do |val|
2496
+ options[:options]['memorySnapshot'] = val.to_s == '' || val.to_s == 'on' || val.to_s == 'true'
2497
+ end
2498
+ opts.on('--for-export [on|off]', String, "For Export? Indicates the snapshot is intended for export to storage.") do |val|
2499
+ options[:options]['forExport'] = val.to_s == '' || val.to_s == 'on' || val.to_s == 'true'
2500
+ end
2501
+ opts.on('--refresh [SECONDS]', String, "Refresh until execution is complete. Default interval is #{default_refresh_interval} seconds.") do |val|
2502
+ options[:refresh_interval] = val.to_s.empty? ? default_refresh_interval : val.to_f
2503
+ end
2504
+ opts.on(nil, '--no-refresh', "Do not refresh" ) do
2505
+ options[:no_refresh] = true
2506
+ end
2507
+ build_standard_add_options(opts, options, [:auto_confirm])
2508
+ opts.footer = <<-EOT
2509
+ Create a snapshot for a host.
2510
+ [host] is required. This is the name or id of a host
2511
+ EOT
2512
+ end
2513
+ optparse.parse!(args)
2514
+ verify_args!(args:args, optparse:optparse, count:1)
2515
+ connect(options)
2516
+ server = find_host_by_name_or_id(args[0])
2517
+ payload = {}
2518
+ if options[:payload]
2519
+ payload = options[:payload]
2520
+ payload.deep_merge!({'snapshot' => parse_passed_options(options)})
2521
+ else
2522
+ payload.deep_merge!({'snapshot' => parse_passed_options(options)})
2523
+ # prompt for name and description
2524
+ name = prompt_value({'fieldName' => 'name', 'type' => 'text', 'fieldLabel' => 'Snapshot Name', 'description' => "Snapshot Name. Default is \"{name}.{timestamp}\""}, options)
2525
+ payload['snapshot']['name'] = name if !name.to_s.empty?
2526
+ description = prompt_value({'fieldName' => 'description', 'type' => 'text', 'fieldLabel' => 'Description', 'description' => "Snapshot Description."}, options)
2527
+ payload['snapshot']['description'] = description if !description.to_s.empty?
2528
+ # need to GET provision type for some settings...
2529
+ provision_type = nil
2530
+ begin
2531
+ provision_type_id = server['computeServerType']['provisionTypeId'] rescue nil
2532
+ if provision_type_id
2533
+ provision_type = @provision_types_interface.get(provision_type_id)['provisionType']
2534
+ end
2535
+ rescue => ex
2536
+ Morpheus::Logging::DarkPrinter.puts "Failed to load provision type!" if Morpheus::Logging.debug?
2537
+ end
2538
+ if provision_type && provision_type['hasMemorySnapshots']
2539
+ # prompt for memorySnapshot
2540
+ memory_snapshot = prompt_value({'fieldName' => 'memorySnapshot', 'type' => 'checkbox', 'fieldLabel' => 'Memory Snapshot?', 'description' => "Snapshot Description."}, options)
2541
+ payload['snapshot']['memorySnapshot'] = memory_snapshot if !memory_snapshot.to_s.empty?
2542
+ end
2543
+ # convert "on" and "off" to true/false
2544
+ payload.booleanize!
2545
+ end
2546
+ unless options[:yes] || ::Morpheus::Cli::OptionTypes::confirm("Are you sure you would like to snapshot the host '#{server['name']}'?", options)
2547
+ exit 1
2548
+ end
2549
+ @servers_interface.setopts(options)
2550
+ if options[:dry_run]
2551
+ print_dry_run @servers_interface.dry.snapshot(server['id'], payload)
2552
+ return
2553
+ end
2554
+ json_response = @servers_interface.snapshot(server['id'], payload)
2555
+ render_response(json_response, options) do
2556
+ print_green_success "Snapshot initiated."
2557
+ process_id = json_response['processIds'][0] rescue nil
2558
+ if process_id
2559
+ unless options[:no_refresh]
2560
+ process = wait_for_process_execution(process_id, options)
2561
+ snapshot_id = process['resultId']
2562
+ if snapshot_id
2563
+ Morpheus::Cli::Snapshots.new.handle(["get", snapshot_id] + (options[:remote] ? ["-r",options[:remote]] : []))
2564
+ end
2565
+ end
2566
+ else
2567
+ # puts "No process returned"
2568
+ end
2569
+ end
2570
+ return 0, nil
2571
+ end
2572
+
2445
2573
  ## Server Devices
2446
2574
 
2447
2575
  def list_devices(args)