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.
- checksums.yaml +4 -4
- data/Dockerfile +1 -1
- data/lib/morpheus/api/clouds_interface.rb +35 -0
- data/lib/morpheus/api/clusters_interface.rb +30 -0
- data/lib/morpheus/api/servers_interface.rb +7 -0
- data/lib/morpheus/api/virtual_images_interface.rb +29 -0
- data/lib/morpheus/cli/commands/clouds.rb +348 -0
- data/lib/morpheus/cli/commands/clusters.rb +361 -1
- data/lib/morpheus/cli/commands/hosts.rb +151 -23
- data/lib/morpheus/cli/commands/instances.rb +76 -21
- data/lib/morpheus/cli/commands/snapshots.rb +4 -0
- data/lib/morpheus/cli/commands/virtual_images.rb +90 -1
- data/lib/morpheus/cli/version.rb +1 -1
- metadata +2 -2
@@ -2915,12 +2915,18 @@ class Morpheus::Cli::Instances
|
|
2915
2915
|
options = {}
|
2916
2916
|
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
2917
2917
|
opts.banner = subcommand_usage("[instance]")
|
2918
|
-
opts.on( '--name VALUE', String, "Snapshot Name. Default is
|
2918
|
+
opts.on( '--name VALUE', String, "Snapshot Name. Default is \"{name}.{timestamp}\"" ) do |val|
|
2919
2919
|
options[:options]['name'] = val
|
2920
2920
|
end
|
2921
2921
|
opts.on( '--description VALUE', String, "Snapshot Description." ) do |val|
|
2922
2922
|
options[:options]['description'] = val
|
2923
2923
|
end
|
2924
|
+
opts.on('--memory-snapshot [on|off]', String, "Memory Snapshot? Whether to include the memory state in the snapshot.") do |val|
|
2925
|
+
options[:options]['memorySnapshot'] = val.to_s == '' || val.to_s == 'on' || val.to_s == 'true'
|
2926
|
+
end
|
2927
|
+
opts.on('--for-export [on|off]', String, "For Export? Indicates the snapshot is intended for export to storage.") do |val|
|
2928
|
+
options[:options]['forExport'] = val.to_s == '' || val.to_s == 'on' || val.to_s == 'true'
|
2929
|
+
end
|
2924
2930
|
opts.on('--refresh [SECONDS]', String, "Refresh until execution is complete. Default interval is #{default_refresh_interval} seconds.") do |val|
|
2925
2931
|
options[:refresh_interval] = val.to_s.empty? ? default_refresh_interval : val.to_f
|
2926
2932
|
end
|
@@ -2937,15 +2943,36 @@ EOT
|
|
2937
2943
|
verify_args!(args:args, optparse:optparse, count:1)
|
2938
2944
|
connect(options)
|
2939
2945
|
instance = find_instance_by_name_or_id(args[0])
|
2940
|
-
|
2941
|
-
exit 1
|
2942
|
-
end
|
2946
|
+
|
2943
2947
|
payload = {}
|
2944
2948
|
if options[:payload]
|
2945
2949
|
payload = options[:payload]
|
2946
2950
|
payload.deep_merge!({'snapshot' => parse_passed_options(options)})
|
2947
2951
|
else
|
2948
2952
|
payload.deep_merge!({'snapshot' => parse_passed_options(options)})
|
2953
|
+
snapshot = payload['snapshot']
|
2954
|
+
# prompt for name and description
|
2955
|
+
name = prompt_value({'fieldName' => 'name', 'type' => 'text', 'fieldLabel' => 'Snapshot Name', 'description' => "Snapshot Name. Default is \"{name}.{timestamp}\""}, options)
|
2956
|
+
snapshot['name'] = name if !name.to_s.empty?
|
2957
|
+
description = prompt_value({'fieldName' => 'description', 'type' => 'text', 'fieldLabel' => 'Description', 'description' => "Snapshot Description."}, options)
|
2958
|
+
snapshot['description'] = description if !description.to_s.empty?
|
2959
|
+
# need to GET provision type for some settings...
|
2960
|
+
provision_type = nil
|
2961
|
+
begin
|
2962
|
+
provision_type = @provision_types_interface.get(instance['layout']['provisionTypeId'])['provisionType']
|
2963
|
+
rescue => ex
|
2964
|
+
Morpheus::Logging::DarkPrinter.puts "Failed to load provision type!" if Morpheus::Logging.debug?
|
2965
|
+
end
|
2966
|
+
if provision_type && provision_type['hasMemorySnapshots']
|
2967
|
+
# prompt for memorySnapshot
|
2968
|
+
memory_snapshot = prompt_value({'fieldName' => 'memorySnapshot', 'type' => 'checkbox', 'fieldLabel' => 'Memory Snapshot?', 'description' => "Snapshot Description."}, options)
|
2969
|
+
snapshot['memorySnapshot'] = memory_snapshot if !memory_snapshot.to_s.empty?
|
2970
|
+
end
|
2971
|
+
# convert "on" and "off" to true/false
|
2972
|
+
payload.booleanize!
|
2973
|
+
end
|
2974
|
+
unless options[:yes] || ::Morpheus::Cli::OptionTypes::confirm("Are you sure you would like to snapshot the instance '#{instance['name']}'?", options)
|
2975
|
+
exit 1
|
2949
2976
|
end
|
2950
2977
|
@instances_interface.setopts(options)
|
2951
2978
|
if options[:dry_run]
|
@@ -3470,17 +3497,18 @@ EOT
|
|
3470
3497
|
def snapshots(args)
|
3471
3498
|
options = {}
|
3472
3499
|
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
3473
|
-
opts.banner = subcommand_usage("[instance]")
|
3500
|
+
opts.banner = subcommand_usage("[instance] [snapshot]")
|
3474
3501
|
# no pagination yet
|
3475
3502
|
# build_standard_list_options(opts, options)
|
3476
|
-
|
3503
|
+
build_standard_list_options(opts, options, [:details])
|
3477
3504
|
opts.footer = <<-EOT
|
3478
3505
|
List snapshots for an instance.
|
3479
3506
|
[instance] is required. This is the name or id of an instance
|
3507
|
+
[snapshot] is optional. this is the name or id a snapshot to filter by.
|
3480
3508
|
EOT
|
3481
3509
|
end
|
3482
3510
|
optparse.parse!(args)
|
3483
|
-
verify_args!(args:args, optparse:optparse,
|
3511
|
+
verify_args!(args:args, optparse:optparse, min:1, max: 2)
|
3484
3512
|
connect(options)
|
3485
3513
|
begin
|
3486
3514
|
instance = find_instance_by_name_or_id(args[0])
|
@@ -3491,25 +3519,52 @@ EOT
|
|
3491
3519
|
return
|
3492
3520
|
end
|
3493
3521
|
json_response = @instances_interface.snapshots(instance['id'], params)
|
3494
|
-
snapshots = json_response['snapshots']
|
3522
|
+
snapshots = json_response['snapshots']
|
3523
|
+
# [snapshots] is done with post api filtering by id or name or externalId
|
3524
|
+
if args[1]
|
3525
|
+
if args[1] =~ /\A\d{1,}\Z/
|
3526
|
+
snapshots = snapshots.select {|it| it['id'].to_s == args[1] }
|
3527
|
+
else
|
3528
|
+
# snapshots = snapshots.select {|it| it['name'] == args[1] || it['externalId'] == args[1] }
|
3529
|
+
# match beginning of name of externalId
|
3530
|
+
snapshots = snapshots.select {|it| it['name'].to_s.index(args[1]) == 0 || it['externalId'].to_s.index(args[1]) == 0 }
|
3531
|
+
end
|
3532
|
+
json_response['snapshots'] = snapshots # update response for -j filtering too
|
3533
|
+
end
|
3495
3534
|
render_response(json_response, options, 'snapshots') do
|
3496
3535
|
print_h1 "Snapshots: #{instance['name']} (#{instance['instanceType']['name']})", [], options
|
3497
3536
|
if snapshots.empty?
|
3498
|
-
|
3537
|
+
if args[1]
|
3538
|
+
print cyan,"No snapshots found for '#{args[1]}'",reset,"\n"
|
3539
|
+
elsif
|
3540
|
+
print cyan,"No snapshots found",reset,"\n"
|
3541
|
+
end
|
3542
|
+
print reset, "\n"
|
3499
3543
|
else
|
3500
|
-
|
3501
|
-
|
3502
|
-
|
3503
|
-
|
3504
|
-
|
3505
|
-
|
3506
|
-
|
3507
|
-
|
3508
|
-
|
3509
|
-
|
3510
|
-
|
3544
|
+
if options[:details]
|
3545
|
+
if snapshots.size > 3
|
3546
|
+
print cyan, "Showing first 3 snapshots. Use the ID to get more details.", reset, "\n"
|
3547
|
+
snapshots = snapshots.first(3) # this actually makes a request for each one here so don't go crazy...
|
3548
|
+
end
|
3549
|
+
snapshots.each do |snapshot|
|
3550
|
+
Morpheus::Cli::Snapshots.new.handle(["get", snapshot['id']] + (options[:remote] ? ["-r",options[:remote]] : []))
|
3551
|
+
end
|
3552
|
+
else
|
3553
|
+
# Snapshots List
|
3554
|
+
snapshot_column_definitions = {
|
3555
|
+
"ID" => lambda {|it| it['id'] },
|
3556
|
+
"Name" => lambda {|it| it['name'] },
|
3557
|
+
"Description" => lambda {|it| it['description'] },
|
3558
|
+
# "Type" => lambda {|it| it['snapshotType'] },
|
3559
|
+
"Date Created" => lambda {|it| format_local_dt(it['snapshotCreated']) },
|
3560
|
+
"Status" => lambda {|it| format_snapshot_status(it) }
|
3561
|
+
}
|
3562
|
+
print cyan
|
3563
|
+
print as_pretty_table(snapshots, snapshot_column_definitions.upcase_keys!, options)
|
3564
|
+
print_results_pagination({size: snapshots.size, total: snapshots.size})
|
3565
|
+
print reset, "\n"
|
3566
|
+
end
|
3511
3567
|
end
|
3512
|
-
print reset, "\n"
|
3513
3568
|
end
|
3514
3569
|
return 0
|
3515
3570
|
rescue RestClient::Exception => e
|
@@ -77,11 +77,15 @@ class Morpheus::Cli::Snapshots
|
|
77
77
|
"Snapshot Type" => 'snapshotType',
|
78
78
|
"Cloud" => lambda {|it| format_name_and_id(it['zone']) },
|
79
79
|
"Datastore" => lambda {|it| format_name_and_id(it['datastore']) },
|
80
|
+
"Memory Snapshot" => lambda {|it| format_boolean(it['memorySnapshot']) },
|
81
|
+
"For Export" => lambda {|it| format_boolean(it['forExport']) },
|
80
82
|
"Parent Snapshot" => lambda {|it| format_name_and_id(it['parentSnapshot']) },
|
81
83
|
"Active" => lambda {|it| format_boolean(it['currentlyActive']) },
|
82
84
|
"Date Created" => lambda {|it| format_local_dt(it['dateCreated']) },
|
83
85
|
"Status" => lambda {|it| format_snapshot_status(it) }
|
84
86
|
}
|
87
|
+
description_cols.delete("Memory Snapshot") if !snapshot['memorySnapshot']
|
88
|
+
description_cols.delete("For Export") if !snapshot['forExport']
|
85
89
|
print_description_list(description_cols, snapshot)
|
86
90
|
|
87
91
|
print reset, "\n"
|
@@ -6,7 +6,8 @@ class Morpheus::Cli::VirtualImages
|
|
6
6
|
include Morpheus::Cli::CliCommand
|
7
7
|
include Morpheus::Cli::ProvisioningHelper
|
8
8
|
|
9
|
-
register_subcommands :list, :get, :add, :add_file, :remove_file, :update, :remove,
|
9
|
+
register_subcommands :list, :get, :add, :add_file, :remove_file, :update, :remove,
|
10
|
+
:convert, {:types => :virtual_image_types}, :download
|
10
11
|
register_subcommands :list_locations, :get_location, :remove_location
|
11
12
|
|
12
13
|
# def initialize()
|
@@ -973,6 +974,94 @@ EOT
|
|
973
974
|
return 0, nil
|
974
975
|
end
|
975
976
|
|
977
|
+
def download(args)
|
978
|
+
full_command_string = "#{command_name} download #{args.join(' ')}".strip
|
979
|
+
options = {}
|
980
|
+
outfile = nil
|
981
|
+
do_overwrite = false
|
982
|
+
do_mkdir = false
|
983
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
984
|
+
opts.banner = subcommand_usage("[image] [local-file]")
|
985
|
+
opts.on( '-f', '--force', "Overwrite existing [local-file] if it exists." ) do
|
986
|
+
do_overwrite = true
|
987
|
+
# do_mkdir = true
|
988
|
+
end
|
989
|
+
opts.on( '-p', '--mkdir', "Create missing directories for [local-file] if they do not exist." ) do
|
990
|
+
do_mkdir = true
|
991
|
+
end
|
992
|
+
build_common_options(opts, options, [:dry_run, :quiet])
|
993
|
+
opts.footer = "Download a virtual image as a .zip file.\n" +
|
994
|
+
"[image] is required. This is the name or id of a virtual image.\n" +
|
995
|
+
"[local-file] is required. This is the full local filepath for the downloaded zip file."
|
996
|
+
end
|
997
|
+
optparse.parse!(args)
|
998
|
+
verify_args!(args:args, optparse:optparse, count:2)
|
999
|
+
connect(options)
|
1000
|
+
|
1001
|
+
virtual_image = find_virtual_image_by_name_or_id(args[0])
|
1002
|
+
return 1 if virtual_image.nil?
|
1003
|
+
outfile = args[1]
|
1004
|
+
# if outfile.end_with?(".zip")
|
1005
|
+
# print_red_alert "[local-file] is invalid. It must use extension .zip: #{outfile}"
|
1006
|
+
# return 1
|
1007
|
+
# end
|
1008
|
+
outfile = File.expand_path(outfile)
|
1009
|
+
if Dir.exist?(outfile)
|
1010
|
+
raise_command_error "[local-file] is invalid. It is the name of an existing directory: #{outfile}", args, optparse
|
1011
|
+
end
|
1012
|
+
destination_dir = File.dirname(outfile)
|
1013
|
+
if !Dir.exist?(destination_dir)
|
1014
|
+
if do_mkdir
|
1015
|
+
print cyan,"Creating local directory #{destination_dir}",reset,"\n"
|
1016
|
+
FileUtils.mkdir_p(destination_dir)
|
1017
|
+
else
|
1018
|
+
raise_command_error "[local-file] is invalid. Directory not found: #{destination_dir}", args, optparse
|
1019
|
+
end
|
1020
|
+
end
|
1021
|
+
if File.exist?(outfile)
|
1022
|
+
if do_overwrite
|
1023
|
+
# uhh need to be careful wih the passed filepath here..
|
1024
|
+
# don't delete, just overwrite.
|
1025
|
+
# File.delete(outfile)
|
1026
|
+
else
|
1027
|
+
raise_command_error "[local-file] is invalid. File already exists: #{outfile}", "Use -f to overwrite the existing file.", args, optparse
|
1028
|
+
end
|
1029
|
+
end
|
1030
|
+
|
1031
|
+
@virtual_images_interface.setopts(options)
|
1032
|
+
if options[:dry_run]
|
1033
|
+
print_dry_run @virtual_images_interface.dry.download_chunked(virtual_image['id'], outfile), full_command_string
|
1034
|
+
return 0, nil
|
1035
|
+
end
|
1036
|
+
if !options[:quiet]
|
1037
|
+
print cyan + "Downloading archive file #{virtual_image['name']} to #{outfile} ... "
|
1038
|
+
end
|
1039
|
+
|
1040
|
+
http_response = @virtual_images_interface.download_chunked(virtual_image['id'], outfile)
|
1041
|
+
|
1042
|
+
# FileUtils.chmod(0600, outfile)
|
1043
|
+
success = http_response.code.to_i == 200
|
1044
|
+
if success
|
1045
|
+
if !options[:quiet]
|
1046
|
+
print green + "SUCCESS" + reset + "\n"
|
1047
|
+
end
|
1048
|
+
return 0, nil
|
1049
|
+
else
|
1050
|
+
if !options[:quiet]
|
1051
|
+
print red + "ERROR" + reset + " HTTP #{http_response.code}" + "\n"
|
1052
|
+
end
|
1053
|
+
# F it, just remove a bad result
|
1054
|
+
if File.exist?(outfile) && File.file?(outfile)
|
1055
|
+
Morpheus::Logging::DarkPrinter.puts "Deleting bad file download: #{outfile}" if Morpheus::Logging.debug?
|
1056
|
+
File.delete(outfile)
|
1057
|
+
end
|
1058
|
+
if options[:debug]
|
1059
|
+
puts_error http_response.inspect
|
1060
|
+
end
|
1061
|
+
return 1, "Error downloading file"
|
1062
|
+
end
|
1063
|
+
end
|
1064
|
+
|
976
1065
|
private
|
977
1066
|
|
978
1067
|
def find_virtual_image_by_name_or_id(val)
|
data/lib/morpheus/cli/version.rb
CHANGED
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: 8.0.
|
4
|
+
version: 8.0.9
|
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: 2025-
|
14
|
+
date: 2025-08-25 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: public_suffix
|