morpheus-cli 9.0.0 → 9.0.2

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.
@@ -15,7 +15,7 @@ class Morpheus::Cli::Hosts
15
15
  :wiki, :update_wiki,
16
16
  :maintenance, :leave_maintenance, :placement,
17
17
  :list_devices, :assign_device, :detach_device, :attach_device,
18
- :snapshot
18
+ :snapshot, :create_linked_clone
19
19
  alias_subcommand :details, :get
20
20
  set_default_subcommand :list
21
21
 
@@ -796,6 +796,10 @@ class Morpheus::Cli::Hosts
796
796
  opts.on("--security-groups LIST", Integer, "Security Groups, comma separated list of security group IDs") do |val|
797
797
  options[:security_groups] = val.split(",").collect {|s| s.strip }.select {|s| !s.to_s.empty? }
798
798
  end
799
+ opts.on("--storage-controller ARRAY", String, "Storage Controllers, JSON array of controller configs. Each entry accepts id, busNumber, typeId, typeName, removable, editable.") do |val|
800
+ options[:options] ||= {}
801
+ options[:options]['storageController'] = JSON.parse(val)
802
+ end
799
803
  opts.on('--tags LIST', String, "Metadata tags in the format 'ping=pong,flash=bang'") do |val|
800
804
  options[:metadata] = val
801
805
  end
@@ -956,6 +960,10 @@ class Morpheus::Cli::Hosts
956
960
  payload['volumes'] = volumes
957
961
  end
958
962
 
963
+ if options[:options] && options[:options]['storageController']
964
+ payload['storageController'] = options[:options]['storageController']
965
+ end
966
+
959
967
  # plan customizations
960
968
  plan_opts = prompt_service_plan_options(service_plan, options, @api_client, {})
961
969
  if plan_opts && !plan_opts.empty?
@@ -1347,6 +1355,10 @@ class Morpheus::Cli::Hosts
1347
1355
  options = {}
1348
1356
  optparse = Morpheus::Cli::OptionParser.new do |opts|
1349
1357
  opts.banner = subcommand_usage("[name]")
1358
+ opts.on("--storage-controller ARRAY", String, "Storage Controllers, JSON array of controller configs. Each entry accepts id, busNumber, typeId, typeName, removable, editable.") do |val|
1359
+ options[:options] ||= {}
1360
+ options[:options]['storageController'] = JSON.parse(val)
1361
+ end
1350
1362
  build_common_options(opts, options, [:options, :json, :dry_run, :quiet, :remote])
1351
1363
  end
1352
1364
  optparse.parse!(args)
@@ -1435,6 +1447,9 @@ class Morpheus::Cli::Hosts
1435
1447
  # only amazon supports this option
1436
1448
  # for now, always do this
1437
1449
  payload[:deleteOriginalVolumes] = true
1450
+ if options[:options] && options[:options]['storageController']
1451
+ payload[:storageController] = options[:options]['storageController']
1452
+ end
1438
1453
  @servers_interface.setopts(options)
1439
1454
  if options[:dry_run]
1440
1455
  print_dry_run @servers_interface.dry.resize(server['id'], payload)
@@ -2716,6 +2731,62 @@ EOT
2716
2731
  return 0, nil
2717
2732
  end
2718
2733
 
2734
+ def create_linked_clone(args)
2735
+ options = {}
2736
+ server = nil
2737
+ snapshot_id = nil
2738
+
2739
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
2740
+ opts.banner = subcommand_usage("[host]")
2741
+ opts.on("--snapshot ID", String, "Snapshot ID") do |val|
2742
+ snapshot_id = val
2743
+ end
2744
+ build_common_options(opts, options, [:auto_confirm, :json, :dry_run, :remote])
2745
+ opts.footer = "Create a linked clone using the selected snapshot of a Host." + "\n" +
2746
+ "[snapshotId] is required. This is the id of the snapshot which the clone will refer to."
2747
+ end
2748
+
2749
+ optparse.parse!(args)
2750
+ if args.count != 1
2751
+ raise_command_error "wrong number of arguments, expected 1 and got (#{args.count}) #{args.join(' ')}\n#{optparse}"
2752
+ end
2753
+ connect(options)
2754
+ begin
2755
+ server = find_host_by_name_or_id(args[0])
2756
+ options[:options]['serverId'] = server['id']
2757
+ begin
2758
+ snapshot_options = @servers_interface.snapshots(server['id'], {})['snapshots'].collect {|it| {'name' => "#{it['name']} (#{it['id']})", 'value' => it['id']} }
2759
+ snapshot_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'snapshotId', 'type' => 'select', 'fieldLabel' => 'Snapshot', 'selectOptions' => snapshot_options, 'required' => true, 'description' => 'Select Snapshot.'}], {}, @api_client, options[:options])
2760
+
2761
+ if !snapshot_prompt['snapshotId'].to_s.empty?
2762
+ snapshot_id = snapshot_prompt['snapshotId']
2763
+ end
2764
+ rescue RestClient::Exception => e
2765
+ puts "Failed to load host snapshots"
2766
+ end
2767
+
2768
+ @servers_interface.setopts(options)
2769
+
2770
+ payload = {}
2771
+ if options[:dry_run]
2772
+ print_dry_run @servers_interface.dry.create_linked_clone(server['id'], snapshot_id, payload)
2773
+ return
2774
+ end
2775
+
2776
+ json_response = @servers_interface.create_linked_clone(server['id'], snapshot_id, payload)
2777
+ if options[:json]
2778
+ puts as_json(json_response, options)
2779
+ else
2780
+ print_green_success "Linked Clone creation initiated."
2781
+ end
2782
+ return 0
2783
+
2784
+ rescue RestClient::Exception => e
2785
+ print_rest_exception(e, options)
2786
+ exit 1
2787
+ end
2788
+ end
2789
+
2719
2790
  ## Server Devices
2720
2791
 
2721
2792
  def list_devices(args)
@@ -315,7 +315,7 @@ class Morpheus::Cli::Instances
315
315
  }
316
316
  row
317
317
  }
318
- columns = [:id, {:name => {:max_width => 50}}, :tenant, :group, :cloud,
318
+ columns = [:id, {:name => {:max_width => 50}}, :labels, :tenant, :group, :cloud,
319
319
  :type, :version, :environment, :plan,
320
320
  {:created => {:display_name => "CREATED"}},
321
321
  {:user => {:display_name => "OWNER", :max_width => 20}},
@@ -465,6 +465,10 @@ class Morpheus::Cli::Instances
465
465
  opts.on("--layout-size NUMBER", Integer, "Apply a multiply factor of containers/vms within the instance") do |val|
466
466
  options[:layout_size] = val.to_i
467
467
  end
468
+ opts.on("--storage-controller ARRAY", String, "Storage Controllers, JSON array of controller configs. Each entry accepts id, busNumber, typeId, typeName, removable, editable. Example: '[{\"busNumber\":\"1\",\"typeName\":\"SCSI\"}]'") do |val|
469
+ options[:options] ||= {}
470
+ options[:options]['storageController'] = JSON.parse(val)
471
+ end
468
472
  opts.on( '-l', '--layout LAYOUT', "Layout ID" ) do |val|
469
473
  options[:layout] = val
470
474
  end
@@ -595,6 +599,9 @@ class Morpheus::Cli::Instances
595
599
  end
596
600
 
597
601
  payload['instance'] ||= {}
602
+ if options[:options] && options[:options]['storageController']
603
+ payload['storageController'] = options[:options]['storageController']
604
+ end
598
605
  if options[:instance_name]
599
606
  payload['instance']['name'] = options[:instance_name]
600
607
  end
@@ -2910,6 +2917,10 @@ class Morpheus::Cli::Instances
2910
2917
  opts.on('--include-network-interfaces','--include-network-interfaces', "Populate payload networkInterfaces with current interfaces") do
2911
2918
  options[:include_nics] = true
2912
2919
  end
2920
+ opts.on("--storage-controller ARRAY", String, "Storage Controllers, JSON array of controller configs. Each entry accepts id, busNumber, typeId, typeName, removable, editable.") do |val|
2921
+ options[:options] ||= {}
2922
+ options[:options]['storageController'] = JSON.parse(val)
2923
+ end
2913
2924
  build_standard_update_options(opts, options)
2914
2925
  end
2915
2926
  optparse.parse!(args)
@@ -3958,7 +3969,7 @@ List snapshots for an instance.
3958
3969
 
3959
3970
  optparse = Morpheus::Cli::OptionParser.new do |opts|
3960
3971
  opts.banner = subcommand_usage("[instance]")
3961
- opts.on("--snapshot ID", String, "Optional snapshot") do |val|
3972
+ opts.on("--snapshot ID", String, "Snapshot ID") do |val|
3962
3973
  snapshot_id = val
3963
3974
  end
3964
3975
  build_common_options(opts, options, [:auto_confirm, :json, :dry_run, :remote])
@@ -5,6 +5,7 @@ class Morpheus::Cli::NetworksCommand
5
5
  include Morpheus::Cli::WhoamiHelper
6
6
  include Morpheus::Cli::InfrastructureHelper
7
7
  include Morpheus::Cli::ProvisioningHelper
8
+ include Morpheus::Cli::AccountsHelper
8
9
 
9
10
  set_command_name :networks
10
11
 
@@ -28,6 +29,8 @@ class Morpheus::Cli::NetworksCommand
28
29
  @groups_interface = @api_client.groups
29
30
  @clouds_interface = @api_client.clouds
30
31
  @options_interface = @api_client.options
32
+ @accounts_interface = @api_client.accounts
33
+ @account_users_interface = @api_client.account_users
31
34
  end
32
35
 
33
36
  def handle(args)
@@ -57,6 +60,13 @@ class Morpheus::Cli::NetworksCommand
57
60
  opts.on('--all-labels LABEL', String, "Filter by labels, must match all of the values") do |val|
58
61
  add_query_parameter(params, 'allLabels', parse_labels(val))
59
62
  end
63
+ opts.on('--include-tenants','--include-tenants', "Include sub tenant networks") do
64
+ options[:include_tenants] = true
65
+ params['includeTenants'] = true
66
+ end
67
+ opts.on('--tenant TENANT', String, "Tenant Name or ID" ) do |val|
68
+ options[:tenant] = val
69
+ end
60
70
  build_common_options(opts, options, [:list, :json, :yaml, :csv, :fields, :json, :dry_run, :remote])
61
71
  opts.footer = "List networks."
62
72
  end
@@ -73,6 +83,18 @@ class Morpheus::Cli::NetworksCommand
73
83
  return 1 if cloud.nil?
74
84
  params['zoneId'] = cloud['id']
75
85
  end
86
+ if options[:tenant]
87
+ if options[:tenant].to_s !~ /\A\d{1,}\Z/
88
+ account = find_account_by_name_or_id(options[:tenant])
89
+ if account.nil?
90
+ return 1, "Tenant not found by name: #{options[:tenant]}"
91
+ else
92
+ params['tenantId'] = account['id']
93
+ end
94
+ else
95
+ params['tenantId'] = options[:tenant]
96
+ end
97
+ end
76
98
  @networks_interface.setopts(options)
77
99
  if options[:dry_run]
78
100
  print_dry_run @networks_interface.dry.list(params)
@@ -109,6 +131,7 @@ class Morpheus::Cli::NetworksCommand
109
131
  type: network['type'] ? network['type']['name'] : '',
110
132
  group: network['group'] ? network['group']['name'] : 'Shared',
111
133
  cloud: network['zone'] ? network['zone']['name'] : '',
134
+ tenant: network['owner'] ? network['owner']['name'] : '',
112
135
  cidr: network['cidr'],
113
136
  pool: network['pool'] ? network['pool']['name'] : '',
114
137
  dhcp: network['dhcpServer'] ? 'Yes' : 'No',
@@ -128,6 +151,7 @@ class Morpheus::Cli::NetworksCommand
128
151
  type: "Subnet",
129
152
  group: network['group'] ? network['group']['name'] : 'Shared',
130
153
  cloud: network['zone'] ? network['zone']['name'] : '',
154
+ tenant: network['owner'] ? network['owner']['name'] : '',
131
155
  cidr: subnet['cidr'],
132
156
  pool: subnet['pool'] ? subnet['pool']['name'] : '',
133
157
  dhcp: subnet['dhcpServer'] ? 'Yes' : 'No',
@@ -139,9 +163,13 @@ class Morpheus::Cli::NetworksCommand
139
163
  end
140
164
  end
141
165
  end
142
- columns = [:id, :name, :labels, :type, :group, :cloud, :cidr, :pool, :dhcp, :subnets, :active, :visibility, :tenants]
166
+ columns = [:id, :name, :labels, :type, :group, :cloud, :tenant, :cidr, :pool, :dhcp, :subnets, :active, :visibility, :tenants]
143
167
  if options[:include_fields]
144
168
  columns = options[:include_fields]
169
+ else
170
+ if !options[:include_tenants] && !options[:tenant]
171
+ columns.delete(:tenant)
172
+ end
145
173
  end
146
174
  print cyan
147
175
  print as_pretty_table(rows, columns, options)
@@ -160,6 +188,9 @@ class Morpheus::Cli::NetworksCommand
160
188
  options = {}
161
189
  optparse = Morpheus::Cli::OptionParser.new do |opts|
162
190
  opts.banner = subcommand_usage("[network]")
191
+ opts.on('--include-tenants','--include-tenants', "Include sub tenant networks when finding network by name") do
192
+ options[:include_tenants] = true
193
+ end
163
194
  build_common_options(opts, options, [:json, :yaml, :csv, :fields, :dry_run, :remote])
164
195
  opts.footer = "Get details about a network." + "\n" +
165
196
  "[network] is required. This is the name or id of a network."
@@ -177,11 +208,11 @@ class Morpheus::Cli::NetworksCommand
177
208
  if args[0].to_s =~ /\A\d{1,}\Z/
178
209
  print_dry_run @networks_interface.dry.get(args[0].to_i)
179
210
  else
180
- print_dry_run @networks_interface.dry.list({name:args[0]})
211
+ print_dry_run @networks_interface.dry.list({name:args[0]}, options[:include_tenants])
181
212
  end
182
213
  return
183
214
  end
184
- network = find_network_by_name_or_id(args[0])
215
+ network = find_network_by_name_or_id(args[0], options[:include_tenants])
185
216
  return 1 if network.nil?
186
217
  json_response = {'network' => network} # skip redundant request
187
218
  # json_response = @networks_interface.get(network['id'])
@@ -206,6 +237,7 @@ class Morpheus::Cli::NetworksCommand
206
237
  "Type" => lambda {|it| it['type'] ? it['type']['name'] : '' },
207
238
  "Group" => lambda {|it| it['group'] ? it['group']['name'] : 'Shared' },
208
239
  "Cloud" => lambda {|it| it['zone'] ? it['zone']['name'] : '' },
240
+ "Tenant" => lambda {|it| it['owner'] ? it['owner']['name'] : '' },
209
241
  "IPv4 Enabled" => 'ipv4Enabled',
210
242
  "IPv6 Enabled" => 'ipv6Enabled',
211
243
  "CIDR" => 'cidr',
@@ -4,13 +4,14 @@ class Morpheus::Cli::StorageVolumes
4
4
  include Morpheus::Cli::CliCommand
5
5
  include Morpheus::Cli::RestCommand
6
6
  include Morpheus::Cli::StorageVolumesHelper
7
+ include Morpheus::Cli::AccountsHelper
7
8
 
8
9
  set_command_name :'storage-volumes'
9
10
  set_command_description "View and manage storage volumes."
10
- register_subcommands :list, :get, :add, :remove, :resize
11
+ register_subcommands :list, :get, :add, :remove, :resize, :update_tenant
11
12
 
12
13
  # RestCommand settings
13
- register_interfaces :storage_volumes, :storage_volume_types
14
+ register_interfaces :storage_volumes, :storage_volume_types, :accounts
14
15
  set_rest_has_type true
15
16
 
16
17
  protected
@@ -28,12 +29,28 @@ class Morpheus::Cli::StorageVolumes
28
29
  opts.on('--category VALUE', String, "Filter by category") do |val|
29
30
  params['category'] = val
30
31
  end
32
+ opts.on('--include-tenants', '--include-tenants', "Include sub-tenant storage volumes (master tenant only)") do
33
+ options[:include_tenants] = true
34
+ params['includeTenants'] = true
35
+ end
36
+ opts.on('--tenant TENANT', String, "Filter by Tenant Name or ID (master tenant only)") do |val|
37
+ options[:tenant] = val
38
+ end
31
39
  # build_standard_list_options(opts, options)
32
40
  super
33
41
  end
34
42
 
35
43
  def parse_list_options!(args, options, params)
36
44
  parse_parameter_as_resource_id!(:storage_server, options, params)
45
+ if options[:tenant]
46
+ if options[:tenant].to_s =~ /\A\d+\Z/
47
+ params['tenantId'] = options[:tenant]
48
+ else
49
+ account = find_account_by_name_or_id(options[:tenant])
50
+ return 1 if account.nil?
51
+ params['tenantId'] = account['id']
52
+ end
53
+ end
37
54
  super
38
55
  end
39
56
 
@@ -122,6 +139,64 @@ class Morpheus::Cli::StorageVolumes
122
139
  end
123
140
  end
124
141
 
142
+ def update_tenant(args)
143
+ options = {}
144
+ params = {}
145
+ tenant_id = nil
146
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
147
+ opts.banner = subcommand_usage("[volume] --tenant TENANT")
148
+ opts.on('--tenant TENANT', String, "Target Tenant (Account) Name or ID to transfer ownership to.") do |val|
149
+ tenant_id = val
150
+ end
151
+ build_standard_update_options(opts, options)
152
+ opts.footer = <<-EOT
153
+ Transfer ownership of a storage volume to another tenant.
154
+ [volume] is required. This is the name or id of a storage volume.
155
+ --tenant is required unless provided via --payload. This is the name or id of the target tenant.
156
+ Only an unattached volume may be transferred. All authorization and business-rule
157
+ validation is performed by the server; its message is shown verbatim on failure.
158
+ EOT
159
+ end
160
+ optparse.parse!(args)
161
+ verify_args!(args:args, optparse:optparse, count:1)
162
+ # CLI-side validation: require a target tenant unless a full payload is supplied.
163
+ # All other authorization and business rules are validated server-side.
164
+ if !options[:payload] && tenant_id.nil?
165
+ raise_command_error "--tenant is required to transfer ownership.\n#{optparse}"
166
+ end
167
+ connect(options)
168
+ volume = find_volume_by_name_or_id(args[0])
169
+ return 1 if volume.nil?
170
+ id = volume['id']
171
+ passed_options = parse_passed_options(options)
172
+ payload = {}
173
+ if options[:payload]
174
+ payload = options[:payload]
175
+ payload.deep_merge!({storage_volume_object_key => passed_options}) unless passed_options.empty?
176
+ else
177
+ account = find_account_by_name_or_id(tenant_id)
178
+ return 1 if account.nil?
179
+ payload[storage_volume_object_key] = {'tenant' => {'id' => account['id']}}
180
+ payload.deep_merge!({storage_volume_object_key => passed_options}) unless passed_options.empty?
181
+ end
182
+ @storage_volumes_interface.setopts(options)
183
+ if options[:dry_run]
184
+ print_dry_run @storage_volumes_interface.dry.update_tenant(id, payload)
185
+ return 0, nil
186
+ end
187
+ json_response = @storage_volumes_interface.update_tenant(id, payload)
188
+ render_response(json_response, options, storage_volume_object_key) do
189
+ record = json_response[storage_volume_object_key]
190
+ owner_name = (record && record['owner'] ? record['owner']['name'] : (record && record['account'] ? record['account']['name'] : tenant_id))
191
+ print_green_success "Transferred storage volume #{record ? record['name'] : id} to tenant #{owner_name}"
192
+ print_h1 rest_label, [], options
193
+ print cyan
194
+ print_description_list(storage_volume_column_definitions(options), record, options)
195
+ print reset,"\n"
196
+ end
197
+ return 0, nil
198
+ end
199
+
125
200
  def find_volume_by_id(id)
126
201
  begin
127
202
  json_response = @storage_volumes_interface.get(id.to_i)