morpheus-cli 6.1.2 → 6.2.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 683efa948a958a0fb4a6def11cb94ae525bdde8cbde0ba1d1aa313adeef3387d
4
- data.tar.gz: c19c0522892a7f96a8398811b89ea4324815f4494011bf6696be1fb362787304
3
+ metadata.gz: b786730f8b5d75b4fd3de84dbb6ab27b91a65302ac7d5a9087d5a2a6c69f4d27
4
+ data.tar.gz: e4750a00de745628f98534029f776e497263e14cc228435c0f27d24f9a4203bd
5
5
  SHA512:
6
- metadata.gz: a1b269a981ec0fcbeecbad62a91ba90f6e7429c26593579185a0a513d0ab5ea5e2394ab11d7bf138570314a418059b6a072e47fb04b0393010dc0f66a463db54
7
- data.tar.gz: 3c211238b4a2dae07c4da54659be1c43e9f0cf7aaf25f7bbfcf53f0c6a0482b43b87b44db541287dfd7201228902902e80c0a1b4a5b538207f3303174940cff9
6
+ metadata.gz: ab990dbe303e1bc744dde6b02c30bab0431f5a7d87a731c758173132973d3dad1b0b61937156a2d378075ee1d9088b55c63887bff673fc78d7bded01f390c943
7
+ data.tar.gz: e56666973915b9020de7715fe42cadaf62391af1c9bbecd50c06b125ddfe16beb6e86f07d4610ceefe961c97b215e8f7c790c24c0eda6d72b72bd3158d80afe1
data/Dockerfile CHANGED
@@ -1,5 +1,5 @@
1
1
  FROM ruby:2.7.5
2
2
 
3
- RUN gem install morpheus-cli -v 6.1.2
3
+ RUN gem install morpheus-cli -v 6.2.0
4
4
 
5
5
  ENTRYPOINT ["morpheus"]
@@ -862,6 +862,14 @@ class Morpheus::APIClient
862
862
  Morpheus::BackupJobsInterface.new(common_interface_options).setopts(@options)
863
863
  end
864
864
 
865
+ def backup_results
866
+ Morpheus::BackupResultsInterface.new(common_interface_options).setopts(@options)
867
+ end
868
+
869
+ def backup_restores
870
+ Morpheus::BackupRestoresInterface.new(common_interface_options).setopts(@options)
871
+ end
872
+
865
873
  def backup_services
866
874
  Morpheus::BackupServicesInterface.new(common_interface_options).setopts(@options)
867
875
  end
@@ -6,4 +6,8 @@ class Morpheus::BackupJobsInterface < Morpheus::RestInterface
6
6
  "/api/backups/jobs"
7
7
  end
8
8
 
9
+ def execute_job(id, payload={}, params={}, headers={})
10
+ execute(method: :post, url: "#{base_path}/#{CGI::escape(id.to_s)}/execute", params: params, payload: payload, headers: headers)
11
+ end
12
+
9
13
  end
@@ -0,0 +1,23 @@
1
+ require 'morpheus/api/api_client'
2
+
3
+ class Morpheus::BackupRestoresInterface < Morpheus::APIClient
4
+
5
+ def base_path
6
+ "/api/backups/restores"
7
+ end
8
+
9
+ def list(params={}, headers={})
10
+ execute(method: :get, url: "#{base_path}", params: params, headers: headers)
11
+ end
12
+
13
+ def get(id, params={}, headers={})
14
+ validate_id!(id)
15
+ execute(method: :get, url: "#{base_path}/#{CGI::escape(id.to_s)}", params: params, headers: headers)
16
+ end
17
+
18
+ def destroy(id, params = {}, headers={})
19
+ validate_id!(id)
20
+ execute(method: :delete, url: "#{base_path}/#{CGI::escape(id.to_s)}", params: params, headers: headers)
21
+ end
22
+
23
+ end
@@ -0,0 +1,28 @@
1
+ require 'morpheus/api/api_client'
2
+
3
+ class Morpheus::BackupResultsInterface < Morpheus::APIClient
4
+
5
+ def base_path
6
+ "/api/backups/results"
7
+ end
8
+
9
+ def list(params={}, headers={})
10
+ execute(method: :get, url: "#{base_path}", params: params, headers: headers)
11
+ end
12
+
13
+ def get(id, params={}, headers={})
14
+ validate_id!(id)
15
+ execute(method: :get, url: "#{base_path}/#{CGI::escape(id.to_s)}", params: params, headers: headers)
16
+ end
17
+
18
+ def cancel(id, payload={}, params={}, headers={})
19
+ validate_id!(id)
20
+ execute(method: :put, url: "#{base_path}/#{CGI::escape(id.to_s)}", params: params, payload: payload, headers: headers)
21
+ end
22
+
23
+ def destroy(id, params = {}, headers={})
24
+ validate_id!(id)
25
+ execute(method: :delete, url: "#{base_path}/#{CGI::escape(id.to_s)}", params: params, headers: headers)
26
+ end
27
+
28
+ end
@@ -10,11 +10,12 @@ class Morpheus::BackupsInterface < Morpheus::RestInterface
10
10
  execute(method: :post, url: "#{base_path}/create", params: params, payload: payload, headers: headers)
11
11
  end
12
12
 
13
- def summary(params={})
14
- execute(method: :get, url: "#{base_path}/summary", params: params)
13
+ def summary(params={}, headers={})
14
+ execute(method: :get, url: "#{base_path}/summary", params: params, headers: headers)
15
15
  end
16
16
 
17
- def history(params={})
18
- execute(method: :get, url: "#{base_path}/history", params: params)
17
+ def execute_backup(id, payload={}, params={}, headers={})
18
+ execute(method: :post, url: "#{base_path}/#{CGI::escape(id.to_s)}/execute", params: params, payload: payload, headers: headers)
19
19
  end
20
+
20
21
  end
@@ -267,6 +267,11 @@ module Morpheus
267
267
  build_standard_post_options(opts, options, includes, excludes)
268
268
  end
269
269
 
270
+ # todo: this can go away once every command is using execute_api()
271
+ def build_standard_add_many_options(opts, options, includes=[], excludes=[])
272
+ build_standard_post_options(opts, options, includes + [:payloads], excludes)
273
+ end
274
+
270
275
  def build_standard_update_options(opts, options, includes=[], excludes=[])
271
276
  build_standard_put_options(opts, options, includes, excludes)
272
277
  end
@@ -551,13 +556,14 @@ module Morpheus
551
556
  end
552
557
  end
553
558
  end if all_option_keys.include?(:payloads)
554
- opts.on('--payloads-ignore-error', "Continue processing payloads if an error occurs. The default behavior is to stop processing when an error occurs.") do
555
- options[:payloads_ignore_error] = true
559
+ opts.on('--ignore-payload-errors', "Continue processing any remaining payloads if an error occurs. The default behavior is to stop processing when an error occurs.") do
560
+ options[:ignore_payload_errors] = true
556
561
  end if all_option_keys.include?(:payloads)
557
562
  when :payloads
558
- # added under when :payloads... just need it here to avoid unknown key error
563
+ # added with :payload too... just need it here to avoid unknown key error
564
+ # todo: remove this when every command supporting :payload is updated to use parse_payload(options) and execute_api(options)
559
565
  when :list
560
- opts.on( '-m', '--max MAX', "Max Results" ) do |val|
566
+ opts.on( '-m', '--max MAX', "Max Results (use -1 for all results)" ) do |val|
561
567
  # api supports max=-1 for all at the moment..
562
568
  if val.to_s == "all" || val.to_s == "-1"
563
569
  options[:max] = "-1"
@@ -1365,6 +1371,11 @@ module Morpheus
1365
1371
  # params['phrase'] = = args.join(" ")
1366
1372
  end
1367
1373
  params.merge!(parse_list_options(options))
1374
+ # query parameters are stored in :params
1375
+ # preserve anything already set in :params by the OptionParser or command specific logic..
1376
+ options[:params] ||= {}
1377
+ options[:params].deep_merge!(params)
1378
+ return options
1368
1379
  end
1369
1380
 
1370
1381
  # The default way to build options for the list command
@@ -1490,50 +1501,118 @@ module Morpheus
1490
1501
  return subtitles
1491
1502
  end
1492
1503
 
1493
- def parse_payload(options={}, object_key=nil)
1494
- payload = nil
1495
- if options[:payload]
1496
- payload = options[:payload]
1497
- # support -O OPTION switch on top of --payload
1498
- apply_options(payload, options, object_key)
1499
- end
1500
- payload
1501
- end
1502
-
1503
- def parse_payloads(options={}, object_key=nil, &block)
1504
+ # Construct the request query parameters from the standard command options
1505
+ # This populates :params with a map of parameters.
1506
+ # This should replace the use of parse_query_options, parse_list_options and parse_list_options! and parse_get_options
1507
+ # which are used everywhere eg. params.merge!(parse_query_options(options))
1508
+ def parse_options(options, params={})
1509
+ params = params ? params.dup : {}
1510
+ # parse_list_options!(args, options, params)
1511
+ # merge in options set with -Q max=3, --query max=3&sort=id
1512
+ params.deep_merge!(options[:query_filters]) if options[:query_filters]
1513
+ # query parameters are stored in :params
1514
+ # preserve anything already set in :params by the OptionParser or command specific logic..
1515
+ options[:params] ||= {}
1516
+ options[:params].deep_merge!(params)
1517
+ # (JSON) body parameters (JSON) are stored in :payload
1518
+ # if options[:payload]
1519
+ # end
1520
+ # ok now call execute_request(@api_client.whoami, :get, nil, options)
1521
+ return options
1522
+ end
1523
+
1524
+ # Parse payload(s) from the standard command options or else invoke the given block.
1525
+ # First looks for --payload or --payload options and if they are nil then the block is executed to establish the payload
1526
+ # By default this also merges all the values passed with -O, --options foo="bar" into payload under the object_key context.
1527
+ # and they are merged under the object_key context (if passed). This can be disabled with apply_options: false
1528
+ #
1529
+ # @param options [Hash] standard command options
1530
+ # @option options [Hash] :payload is a Hash of objects to serialize as the payload
1531
+ # @option options [Hash] :payloads is an array of payload objects@yield [street_name] Invokes the block with a street name for eac
1532
+ # This is silly and should go away, instead you should iterate in the terminal environment
1533
+ # @option options [Boolean] :apply_options can be set to false to skip -O options merge
1534
+ # @param object_key [String] The name of the object being constructed, -O --options will be merged under this context.
1535
+ # @return array of payloads
1536
+ # @yield [payload] Invokes the block to establish :payload (only when --payload(s) is not used)
1537
+ def parse_payload(options, object_key=nil, &block)
1538
+ # populate options[:params] here too
1539
+ parse_options(options)
1504
1540
  payloads = []
1541
+ # todo: only need to support a a single payload here, :payloads is silly and is going away
1505
1542
  if options[:payload]
1506
1543
  # --payload option was used
1507
1544
  payload = options[:payload]
1508
1545
  # support -O OPTION switch on top of --payload
1509
- apply_options(payload, options, object_key)
1546
+ apply_options(payload, options, object_key) unless options[:apply_options] == false
1510
1547
  payloads << payload
1511
1548
  elsif options[:payloads]
1512
1549
  # --payloads option was used
1513
1550
  payloads = options[:payloads]
1514
- # payloads.each { |it| apply_options(it, options, object_key) }
1551
+ # support -O OPTION switch on top of --payloads
1552
+ payloads.each do |payload|
1553
+ apply_options(payload, options, object_key) unless options[:apply_options] == false
1554
+ end
1515
1555
  else
1516
- # default is to construct one using the block
1556
+ # yield to block to construct the payload,
1557
+ # this is typically where prompting for inputs with optionTypes happens
1517
1558
  payload = {}
1518
- apply_options(payload, options, object_key)
1559
+ apply_options(payload, options, object_key) unless options[:apply_options] == false
1519
1560
  if block_given?
1520
- result = yield payload
1521
- #payload = result if result
1561
+ yield payload
1522
1562
  end
1523
1563
  payloads << payload
1564
+ options[:payload] = payload
1524
1565
  end
1525
- return payloads
1566
+ return payloads.first
1526
1567
  end
1527
1568
 
1528
- def process_payloads(payloads, options, &block)
1569
+ # support -O OPTION switch
1570
+ def apply_options(payload, options, object_key=nil)
1571
+ payload ||= {}
1572
+ if options[:options]
1573
+ # allow options[:object_key] to be used
1574
+ object_key = object_key ? object_key : options[:object_key]
1575
+ # could use parse_passed_options() here to support exclusion of certain options
1576
+ #passed_options = parse_passed_options(options, options[:apply_options] || {})
1577
+ passed_options = options[:options].reject {|k,v| k.is_a?(Symbol)}
1578
+ if object_key
1579
+ payload.deep_merge!({object_key => passed_options})
1580
+ else
1581
+ payload.deep_merge!(passed_options)
1582
+ end
1583
+ end
1584
+ payload
1585
+ end
1586
+
1587
+ # Executes the block with each payload (:payload or :payloads as parsed from --payload FILE and --payloads --PATH)
1588
+ # This is a wrapper to support execution on 1-N payloads
1589
+ # It also looks for --ignore-payload-errors behavior to continue processing
1590
+ # It is up to the block to actually make the api request
1591
+ # @param options [Hash] standard command options
1592
+ # @raise [Error] if there is no :payload or :payloads defined.
1593
+ # @yield [payload] Yields each payload to the block
1594
+ # @return parsed command result of the last return value of the block ie. [0, nil]
1595
+ def handle_each_payload(options, &block)
1596
+ payloads = []
1597
+ if options[:payloads]
1598
+ payloads = options[:payloads]
1599
+ elsif options[:payload]
1600
+ payloads << options[:payload]
1601
+ else
1602
+ raise "handle_each_payload() requires :payload or :payloads"
1603
+ end
1529
1604
  if !payloads.is_a?(Array) || payloads.compact.empty?
1530
- raise "process_payloads() requires an Array of at least one payload and instead got: (#{payloads.class}) #{payloads.inspect}"
1605
+ raise "handle_each_payload() requires a payload"
1606
+ end
1607
+ if !block_given?
1608
+ raise "handle_each_payload() requires a block to process the payload(s) with"
1531
1609
  end
1532
1610
  results = []
1533
1611
  payloads.each do |payload|
1534
1612
  begin
1535
1613
  result = yield payload
1536
- results << [0, nil]
1614
+ results << Morpheus::Cli::CliRegistry.parse_command_result(result)
1615
+ #results << [0, nil]
1537
1616
  rescue => e
1538
1617
  if options[:payloads_ignore_error]
1539
1618
  # results << [1, e.message]
@@ -1548,16 +1627,67 @@ module Morpheus
1548
1627
  return results.last
1549
1628
  end
1550
1629
 
1551
- def build_payload(options, object_key=nil)
1552
- payload = {}
1553
- if options[:payload]
1554
- parse_payload(options, object_key)
1630
+ # Standard handler for all commands that execute an api request.
1631
+ # This looks for a payload that can be set with --payload or --payloads or the default prompting
1632
+ # It is up to the block to handle the rendering behavior
1633
+ # @param api_interface [APIClient] An APIClient instance
1634
+ # @param api_method [String or Symbol] api method to invoke eg. :get, :create, :update, :destroy
1635
+ # @param args [Array] Array of arguments to be passed to the api method, usually just the [payload] or [payload, query_params]
1636
+ # @param options [Hash] options
1637
+ # @param object_key [String or Symbol] name of object being constructed, used by default rendering eg. --fields id,name
1638
+ # @yield [json_response] Invokes the block with the json response to handle rendering.
1639
+ # @return parsed command result of the last block.call(json_response)
1640
+ #
1641
+ # @example Fetch first 100 backups
1642
+ # execute_api(@api_client.backups, :list, [{"max" => 100}], options) do |json_response|
1643
+ # print_green_success "Fetched first #{json_response['backups'].size} of #{json_response['meta']['total']} backups"
1644
+ # end
1645
+ #
1646
+ # @example Create a backup, uses POST with options[:payload] as the body
1647
+ # @options[:payload] = {"backup" => { }}
1648
+ # execute_api(@api_client.backups, :create, nil, options, 'backup') do |json_response|
1649
+ # print_green_success "Added backup #{json_response['backup']['name']}"
1650
+ # end
1651
+ #
1652
+ def execute_api(api_interface, api_method, args, options, object_key=nil, &block)
1653
+ args = args.is_a?(Array) ? args : [args].compact
1654
+ if options[:payload] || options[:payloads]
1655
+ execute_api_payload(api_interface, api_method, args, options, object_key, &block)
1656
+ else
1657
+ execute_api_request(api_interface, api_method, args, options, object_key, &block)
1658
+ end
1659
+ end
1660
+
1661
+ # Standard handler for all POST commands that send a request for a payload
1662
+ # This supports the --payloads option for 1-N payloads, that is silly and will probably go away
1663
+ def execute_api_payload(api_interface, api_method, args, options, object_key=nil, &block)
1664
+ handle_each_payload(options) do |payload|
1665
+ execute_api_request(api_interface, api_method, (args || []) + [payload], options, object_key, &block)
1666
+ end
1667
+ end
1668
+
1669
+ # Standard handler for executing any API request
1670
+ # Supports the --dry-run option and standard rendering options --json, --yaml, --fields, --select, etc.
1671
+ def execute_api_request(api_interface, api_method, args, options, object_key=nil, &block)
1672
+ args = args.is_a?(Array) ? args : [args].compact # allow caller to pass [payload] or payload
1673
+ api_interface.setopts(options) # this is needed to support --timeout and --headers
1674
+ # this assumes the interface parameter order is: [payload, params] and not vice versa
1675
+ if options[:params] && !options[:params].empty?
1676
+ args << options[:params]
1677
+ end
1678
+ if options[:dry_run]
1679
+ # this is a dry run
1680
+ dry_response = api_interface.dry.send(api_method, *args)
1681
+ print_dry_run(dry_response, options)
1682
+ return 0, nil
1555
1683
  else
1556
- apply_options(payload, options, object_key)
1684
+ # execute the request and render the result
1685
+ json_response = api_interface.send(api_method, *args)
1686
+ return render_response(json_response, options, object_key, &block)
1557
1687
  end
1558
- return payload
1559
1688
  end
1560
1689
 
1690
+ # Parse an array from a string (csv)
1561
1691
  def parse_array(val, opts={})
1562
1692
  opts = {strip:true, allow_blank:false}.merge(opts)
1563
1693
  values = []
@@ -1580,19 +1710,6 @@ module Morpheus
1580
1710
  parse_array(val, {strip:true, allow_blank:false})
1581
1711
  end
1582
1712
 
1583
- # support -O OPTION switch
1584
- def apply_options(payload, options, object_key=nil)
1585
- payload ||= {}
1586
- if options[:options]
1587
- if object_key
1588
- payload.deep_merge!({object_key => options[:options].reject {|k,v| k.is_a?(Symbol)}})
1589
- else
1590
- payload.deep_merge!(options[:options].reject {|k,v| k.is_a?(Symbol)})
1591
- end
1592
- end
1593
- payload
1594
- end
1595
-
1596
1713
  def validate_outfile(outfile, options)
1597
1714
  full_filename = File.expand_path(outfile)
1598
1715
  outdir = File.dirname(full_filename)
@@ -1619,6 +1736,8 @@ module Morpheus
1619
1736
  # basic rendering for options :json, :yml, :csv, :quiet, and :outfile
1620
1737
  # returns the string rendered, or nil if nothing was rendered.
1621
1738
  def render_response(json_response, options, object_key=nil, &block)
1739
+ # allow options[:object_key] to be used
1740
+ object_key = object_key ? object_key : options[:object_key]
1622
1741
  output = nil
1623
1742
  if options[:select_fields]
1624
1743
  # support foos get --raw-select foo.x,foo.y,foo.z
@@ -1676,7 +1795,7 @@ module Morpheus
1676
1795
  else
1677
1796
  # no render happened, so calling the block if given
1678
1797
  if block_given?
1679
- result = yield
1798
+ result = yield json_response
1680
1799
  if result
1681
1800
  return result
1682
1801
  else
@@ -1903,6 +2022,14 @@ module Morpheus
1903
2022
  # add aliases here as needed
1904
2023
  if key == "cloud"
1905
2024
  key = "zone"
2025
+ elsif key == "site"
2026
+ key = "group"
2027
+ elsif key == "backupJob"
2028
+ key = "job"
2029
+ elsif key == "backupResult"
2030
+ key = "result"
2031
+ elsif key == "backupRestore"
2032
+ key = "restore"
1906
2033
  end
1907
2034
  return key
1908
2035
  end
@@ -231,9 +231,7 @@ class Morpheus::Cli::ApplianceSettingsCommand
231
231
  end
232
232
 
233
233
  begin
234
- payload = parse_payload(options)
235
-
236
- if !payload
234
+ parse_payload(options) do |payload|
237
235
  available_zone_types = @appliance_settings_interface.cloud_types['zoneTypes']
238
236
 
239
237
  if options[:enableZoneTypes]
@@ -283,29 +281,19 @@ class Morpheus::Cli::ApplianceSettingsCommand
283
281
  exit 1
284
282
  end
285
283
  end
286
-
287
- payload = {'applianceSettings' => params}
284
+ payload['applianceSettings'] = params
288
285
  end
289
-
290
- @appliance_settings_interface.setopts(options)
291
- if options[:dry_run]
292
- print_dry_run @appliance_settings_interface.dry.update(payload)
293
- return
294
- end
295
- json_response = @appliance_settings_interface.update(payload)
296
-
297
- if options[:json]
298
- puts as_json(json_response, options)
299
- elsif !options[:quiet]
286
+ execute_api(@appliance_settings_interface, :update, nil, options, "applianceSettings") do |json_response|
300
287
  if json_response['success']
301
288
  print_green_success "Updated appliance settings"
302
289
  get([] + (options[:remote] ? ["-r",options[:remote]] : []))
303
290
  else
304
- print_red_alert "Error updating appliance settings: #{json_response['msg'] || json_response['errors']}"
291
+ #todo: API should return 400 on error and then this is not needed
292
+ #print_red_alert "Error updating appliance settings"
293
+ print_rest_errors(json_response)
294
+ [1, "Error updating appliance settings"]
305
295
  end
306
296
  end
307
- return 0
308
-
309
297
  rescue RestClient::Exception => e
310
298
  print_rest_exception(e, options)
311
299
  exit 1
@@ -790,7 +790,7 @@ class Morpheus::Cli::Apps
790
790
  if !instance['connectionInfo'].nil? && instance['connectionInfo'].empty? == false
791
791
  connection_string = "#{instance['connectionInfo'][0]['ip']}:#{instance['connectionInfo'][0]['port']}"
792
792
  end
793
- {id: instance['id'], name: instance['name'], connection: connection_string, environment: instance['instanceContext'], nodes: (instance['containers'] || []).count, status: format_instance_status(instance), type: instance['instanceType']['name'], group: !instance['group'].nil? ? instance['group']['name'] : nil, cloud: !instance['cloud'].nil? ? instance['cloud']['name'] : nil}
793
+ {id: instance['id'], name: instance['displayName'] ? instance['displayName'] : instance['name'], connection: connection_string, environment: instance['instanceContext'], nodes: (instance['containers'] || []).count, status: format_instance_status(instance), type: instance['instanceType']['name'], group: !instance['group'].nil? ? instance['group']['name'] : nil, cloud: !instance['cloud'].nil? ? instance['cloud']['name'] : nil}
794
794
  end
795
795
  instances_rows = instances_rows.sort {|x,y| x[:id] <=> y[:id] } #oldest to newest..
796
796
  print cyan