morpheus-cli 5.4.0 → 5.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.
- checksums.yaml +4 -4
- data/Dockerfile +1 -1
- data/lib/morpheus/api/account_users_interface.rb +68 -0
- data/lib/morpheus/api/api_client.rb +51 -9
- data/lib/morpheus/api/audit_interface.rb +9 -0
- data/lib/morpheus/api/instances_interface.rb +21 -0
- data/lib/morpheus/api/load_balancer_monitors_interface.rb +9 -0
- data/lib/morpheus/api/load_balancer_pools_interface.rb +4 -4
- data/lib/morpheus/api/load_balancer_profiles_interface.rb +4 -5
- data/lib/morpheus/api/load_balancer_virtual_servers_interface.rb +13 -4
- data/lib/morpheus/api/load_balancers_interface.rb +5 -0
- data/lib/morpheus/api/network_routers_interface.rb +9 -0
- data/lib/morpheus/api/network_static_routes_interface.rb +36 -0
- data/lib/morpheus/api/read_interface.rb +4 -3
- data/lib/morpheus/api/rest_interface.rb +3 -3
- data/lib/morpheus/api/secondary_read_interface.rb +1 -1
- data/lib/morpheus/api/secondary_rest_interface.rb +19 -19
- data/lib/morpheus/api/storage_server_types_interface.rb +14 -0
- data/lib/morpheus/api/storage_servers_interface.rb +9 -0
- data/lib/morpheus/api/storage_volume_types_interface.rb +9 -0
- data/lib/morpheus/api/storage_volumes_interface.rb +9 -0
- data/lib/morpheus/api/users_interface.rb +16 -63
- data/lib/morpheus/cli/cli_command.rb +253 -5
- data/lib/morpheus/cli/cli_registry.rb +1 -1
- data/lib/morpheus/cli/commands/alias_command.rb +1 -1
- data/lib/morpheus/cli/commands/apps.rb +14 -78
- data/lib/morpheus/cli/commands/audit.rb +188 -0
- data/lib/morpheus/cli/commands/blueprints_command.rb +1 -1
- data/lib/morpheus/cli/commands/change_password_command.rb +4 -4
- data/lib/morpheus/cli/commands/clusters.rb +37 -12
- data/lib/morpheus/cli/commands/hosts.rb +15 -15
- data/lib/morpheus/cli/commands/instances.rb +109 -2
- data/lib/morpheus/cli/commands/load_balancer_monitors.rb +71 -0
- data/lib/morpheus/cli/commands/load_balancer_pools.rb +30 -50
- data/lib/morpheus/cli/commands/load_balancer_profiles.rb +65 -0
- data/lib/morpheus/cli/commands/load_balancer_types.rb +9 -4
- data/lib/morpheus/cli/commands/load_balancer_virtual_servers.rb +77 -57
- data/lib/morpheus/cli/commands/load_balancers.rb +93 -6
- data/lib/morpheus/cli/commands/network_firewalls_command.rb +22 -5
- data/lib/morpheus/cli/commands/network_routers_command.rb +96 -45
- data/lib/morpheus/cli/commands/network_static_routes_command.rb +446 -0
- data/lib/morpheus/cli/commands/network_transport_zones_command.rb +4 -4
- data/lib/morpheus/cli/commands/open_command.rb +30 -0
- data/lib/morpheus/cli/commands/options.rb +98 -0
- data/lib/morpheus/cli/commands/policies_command.rb +1 -1
- data/lib/morpheus/cli/commands/prices_command.rb +7 -7
- data/lib/morpheus/cli/commands/remote.rb +4 -2
- data/lib/morpheus/cli/commands/roles.rb +1 -1
- data/lib/morpheus/cli/commands/shell.rb +2 -2
- data/lib/morpheus/cli/commands/storage_server_types.rb +50 -0
- data/lib/morpheus/cli/commands/storage_servers.rb +122 -0
- data/lib/morpheus/cli/commands/storage_volume_types.rb +50 -0
- data/lib/morpheus/cli/commands/storage_volumes.rb +103 -0
- data/lib/morpheus/cli/commands/tenants_command.rb +1 -1
- data/lib/morpheus/cli/commands/user_groups_command.rb +1 -1
- data/lib/morpheus/cli/commands/user_settings_command.rb +2 -1
- data/lib/morpheus/cli/commands/user_sources_command.rb +1 -1
- data/lib/morpheus/cli/commands/users.rb +28 -28
- data/lib/morpheus/cli/commands/view.rb +102 -0
- data/lib/morpheus/cli/mixins/accounts_helper.rb +5 -5
- data/lib/morpheus/cli/mixins/load_balancers_helper.rb +24 -4
- data/lib/morpheus/cli/mixins/print_helper.rb +50 -18
- data/lib/morpheus/cli/mixins/processes_helper.rb +1 -2
- data/lib/morpheus/cli/mixins/provisioning_helper.rb +15 -5
- data/lib/morpheus/cli/mixins/rest_command.rb +145 -73
- data/lib/morpheus/cli/mixins/secondary_rest_command.rb +174 -81
- data/lib/morpheus/cli/mixins/storage_servers_helper.rb +156 -0
- data/lib/morpheus/cli/mixins/storage_volumes_helper.rb +119 -0
- data/lib/morpheus/cli/option_types.rb +45 -24
- data/lib/morpheus/cli/version.rb +1 -1
- data/lib/morpheus/cli.rb +1 -0
- data/lib/morpheus/ext/string.rb +29 -6
- data/lib/morpheus/routes.rb +238 -0
- data/lib/morpheus/util.rb +6 -1
- metadata +29 -8
@@ -1011,7 +1011,7 @@ module Morpheus
|
|
1011
1011
|
# meh, could deprecate and make subcommand define handle() itself
|
1012
1012
|
# if args.count == 0 && default_subcommand
|
1013
1013
|
# # p "using default subcommand #{default_subcommand}"
|
1014
|
-
# return
|
1014
|
+
# return send(default_subcommand, args || [])
|
1015
1015
|
# end
|
1016
1016
|
subcommand_name = args[0]
|
1017
1017
|
if args.empty?
|
@@ -1032,7 +1032,7 @@ module Morpheus
|
|
1032
1032
|
error_msg = "'#{command_name} #{subcommand_name}' is not a #{prog_name} command.\n#{full_command_usage}"
|
1033
1033
|
raise CommandNotFoundError.new(error_msg)
|
1034
1034
|
end
|
1035
|
-
|
1035
|
+
send(cmd_method, args[1..-1])
|
1036
1036
|
end
|
1037
1037
|
|
1038
1038
|
def handle(args)
|
@@ -1230,6 +1230,72 @@ module Morpheus
|
|
1230
1230
|
true
|
1231
1231
|
end
|
1232
1232
|
|
1233
|
+
# The default way to build options for the list command
|
1234
|
+
# @param [OptionParser] opts
|
1235
|
+
# @param [Hash] options
|
1236
|
+
# @param [Hash] params
|
1237
|
+
def build_list_options(opts, options, params)
|
1238
|
+
build_standard_list_options(opts, options)
|
1239
|
+
end
|
1240
|
+
|
1241
|
+
# The default way to parse options for the list command
|
1242
|
+
# @param [Array] args
|
1243
|
+
# @param [Hash] options
|
1244
|
+
# @param [Hash] params
|
1245
|
+
def parse_list_options!(args, options, params)
|
1246
|
+
if args.count > 0
|
1247
|
+
options[:phrase] = args.join(" ")
|
1248
|
+
# params['phrase'] = = args.join(" ")
|
1249
|
+
end
|
1250
|
+
params.merge!(parse_list_options(options))
|
1251
|
+
end
|
1252
|
+
|
1253
|
+
# The default way to build options for the list command
|
1254
|
+
# @param [OptionParser] opts
|
1255
|
+
# @param [Hash] options
|
1256
|
+
# @param [Hash] params
|
1257
|
+
def build_get_options(opts, options, params)
|
1258
|
+
build_standard_get_options(opts, options)
|
1259
|
+
end
|
1260
|
+
|
1261
|
+
# The default way to parse options for the get command
|
1262
|
+
# @param [OptionParser] opts
|
1263
|
+
# @param [Hash] options
|
1264
|
+
# @param [Hash] params
|
1265
|
+
def parse_get_options!(args, options, params)
|
1266
|
+
params.merge!(parse_query_options(options))
|
1267
|
+
end
|
1268
|
+
|
1269
|
+
# The default way to parse options for the get command
|
1270
|
+
# @param type [string]
|
1271
|
+
# @param options [Hash] The command options
|
1272
|
+
# @param params [Hash] The query parameters the output is being appended to
|
1273
|
+
# @param param_name [String]
|
1274
|
+
# @param lookup_ids [Boolean] Also lookup ids to make sure they exist or else error
|
1275
|
+
# @return
|
1276
|
+
def parse_parameter_as_resource_id!(type, options, params, param_name=nil, lookup_ids=false)
|
1277
|
+
# type = type.to_s.singularize
|
1278
|
+
if options.key?(type)
|
1279
|
+
val = options[type].to_s
|
1280
|
+
param_name ||= "#{type.to_s.camelcase}Id"
|
1281
|
+
if val
|
1282
|
+
if val.to_s !~ /\A\d{1,}\Z/ || lookup_ids
|
1283
|
+
record = find_by_name(type, val)
|
1284
|
+
if record.nil?
|
1285
|
+
# avoid double error render by exiting here, ew
|
1286
|
+
exit 1
|
1287
|
+
raise_command_error "Storage Server not found for '#{val}'"
|
1288
|
+
end
|
1289
|
+
params[param_name] = record['id']
|
1290
|
+
else
|
1291
|
+
params[param_name] = val
|
1292
|
+
end
|
1293
|
+
return params[param_name]
|
1294
|
+
end
|
1295
|
+
end
|
1296
|
+
return nil
|
1297
|
+
end
|
1298
|
+
|
1233
1299
|
# parse the parameters provided by the common :list options
|
1234
1300
|
# this includes the :query options too via parse_query_options().
|
1235
1301
|
# returns Hash of params the format {"phrase": => "foobar", "max": 100}
|
@@ -1244,7 +1310,11 @@ module Morpheus
|
|
1244
1310
|
end
|
1245
1311
|
# arbitrary filters
|
1246
1312
|
list_params.merge!(parse_query_options(options))
|
1247
|
-
|
1313
|
+
# ok, any string keys in options can become query parameters, eg. options['name'] = 'foobar'
|
1314
|
+
# do it!
|
1315
|
+
# options.each do |k, v|
|
1316
|
+
# list_params[k] = v
|
1317
|
+
# end
|
1248
1318
|
return list_params
|
1249
1319
|
end
|
1250
1320
|
|
@@ -1365,7 +1435,7 @@ module Morpheus
|
|
1365
1435
|
full_outfile = File.expand_path(options[:outfile])
|
1366
1436
|
if output
|
1367
1437
|
print_to_file(output, options[:outfile], options[:overwrite])
|
1368
|
-
print "#{cyan}Wrote output to file #{options[:outfile]} (#{File.size(full_outfile)}
|
1438
|
+
print "#{cyan}Wrote output to file #{options[:outfile]} (#{format_bytes(File.size(full_outfile))})\n" unless options[:quiet]
|
1369
1439
|
else
|
1370
1440
|
# uhhh ok lets try this
|
1371
1441
|
Morpheus::Logging::DarkPrinter.puts "using experimental feature: --out without a common format like json, yml or csv" if Morpheus::Logging.debug?
|
@@ -1373,7 +1443,7 @@ module Morpheus
|
|
1373
1443
|
if result && result != 0
|
1374
1444
|
return result
|
1375
1445
|
end
|
1376
|
-
print "#{cyan}Wrote output to file #{options[:outfile]} (#{File.size(full_outfile)}
|
1446
|
+
print "#{cyan}Wrote output to file #{options[:outfile]} (#{format_bytes(File.size(full_outfile))})\n" unless options[:quiet]
|
1377
1447
|
return 0, nil
|
1378
1448
|
end
|
1379
1449
|
else
|
@@ -1404,6 +1474,184 @@ module Morpheus
|
|
1404
1474
|
|
1405
1475
|
alias :render_with_format :render_response
|
1406
1476
|
|
1477
|
+
# Dynamic find methods to load a record of any type
|
1478
|
+
# def find_by_name_or_id(type, val)
|
1479
|
+
# interface = instance_variable_get "@#{type}s_interface"
|
1480
|
+
# typeCamelCase = type.gsub(/(?:^|_)([a-z])/) do $1.upcase end
|
1481
|
+
# typeCamelCase = typeCamelCase[0, 1].downcase + typeCamelCase[1..-1]
|
1482
|
+
# (val.to_s =~ /\A\d{1,}\Z/) ? interface.get(val.to_i)[typeCamelCase] : interface.list({'name' => val})["#{typeCamelCase}s"].first
|
1483
|
+
# end
|
1484
|
+
|
1485
|
+
# Find a resource by type and name or id
|
1486
|
+
# @param type [String of Symbol] Type of resource formatted as singular, lowerscore with underscores.
|
1487
|
+
# @param *id [String or Numeric] ID of resource, multiple arguments may be passed when using a secondary interface where parent_id, id is required.
|
1488
|
+
# Example: find_by_name_or_id("instance", "K2")
|
1489
|
+
# find_by_name_or_id("storage_volume", 42)
|
1490
|
+
# find_by_name_or_id("instance", "My Instance")
|
1491
|
+
# find_by_name_or_id("load_balancer_pool", load_balancer_id, id)
|
1492
|
+
def find_by_name_or_id(*args)
|
1493
|
+
val = args.last
|
1494
|
+
if val.to_s =~ /\A\d{1,}\Z/
|
1495
|
+
return find_by_id(*args)
|
1496
|
+
else
|
1497
|
+
return find_by_name(*args)
|
1498
|
+
end
|
1499
|
+
end
|
1500
|
+
|
1501
|
+
|
1502
|
+
# Find a resource by type and id
|
1503
|
+
# Usage: find_by_name_or_id("app", 3)
|
1504
|
+
def find_by_id(*args)
|
1505
|
+
#Morpheus::Logging::DarkPrinter.puts "find_by_id(#{args.join(', ')})" if Morpheus::Logging.debug?
|
1506
|
+
# type, ids = args.first, args[1..-1]
|
1507
|
+
type, *ids = args
|
1508
|
+
type = type.to_s.singularize.underscore
|
1509
|
+
# still relying on the command or helper to define these _label and _key methods
|
1510
|
+
label = send("#{type}_label")
|
1511
|
+
object_key = send("#{type}_object_key")
|
1512
|
+
interface_name = "@#{type.pluralize}_interface"
|
1513
|
+
interface = instance_variable_get(interface_name)
|
1514
|
+
if interface.nil?
|
1515
|
+
raise "#{self.class} has not defined interface #{interface_name}"
|
1516
|
+
end
|
1517
|
+
begin
|
1518
|
+
json_response = interface.get(*ids)
|
1519
|
+
return json_response[object_key]
|
1520
|
+
rescue RestClient::Exception => e
|
1521
|
+
if e.response && e.response.code == 404
|
1522
|
+
print_red_alert "#{label} not found by id #{ids.last}"
|
1523
|
+
return nil
|
1524
|
+
else
|
1525
|
+
raise e
|
1526
|
+
end
|
1527
|
+
end
|
1528
|
+
end
|
1529
|
+
|
1530
|
+
# Find a record by type and name
|
1531
|
+
# Usage: find_by_name_or_id("network", "Skynet")
|
1532
|
+
def find_by_name(*args)
|
1533
|
+
#Morpheus::Logging::DarkPrinter.puts "find_by_name(#{args.join(', ')})" if Morpheus::Logging.debug?
|
1534
|
+
# type, ids = args.first, args[1..-1]
|
1535
|
+
type, *ids = args
|
1536
|
+
type = type.to_s.singularize.underscore
|
1537
|
+
val = ids.pop
|
1538
|
+
params = {}
|
1539
|
+
name_property = 'name'
|
1540
|
+
if type == 'user'
|
1541
|
+
name_property = 'username'
|
1542
|
+
params['global'] = 'true'
|
1543
|
+
end
|
1544
|
+
params[name_property] = val.to_s
|
1545
|
+
request_args = ids + [params]
|
1546
|
+
request_args.unshift(type)
|
1547
|
+
records = find_all(*request_args)
|
1548
|
+
# still relying on the command or helper to define these _label and _key methods
|
1549
|
+
label = respond_to?("#{type}_label", true) ? send("#{type}_label") : type.titleize
|
1550
|
+
if records.empty?
|
1551
|
+
print_red_alert "#{label} not found by name '#{val}'"
|
1552
|
+
return nil
|
1553
|
+
elsif records.size > 1
|
1554
|
+
print_red_alert "More than one #{label.downcase} found by #{name_property} '#{val}'"
|
1555
|
+
print_error "\n"
|
1556
|
+
if type == "user"
|
1557
|
+
puts_error as_pretty_table(records, [:id, :username, {"FIRST NAME" => "firstName"}, {"LAST NAME" => "lastName"}, {"TENANT" => lambda {|it| it['account']['name'] rescue ''}}], {color:red})
|
1558
|
+
else
|
1559
|
+
puts_error as_pretty_table(records, [:id, :name], {color:red})
|
1560
|
+
end
|
1561
|
+
print_red_alert "Try using ID instead"
|
1562
|
+
print_error reset,"\n"
|
1563
|
+
return nil
|
1564
|
+
else
|
1565
|
+
return records[0]
|
1566
|
+
end
|
1567
|
+
end
|
1568
|
+
|
1569
|
+
# Load a list of records by type
|
1570
|
+
# @example Find an app by id
|
1571
|
+
# find_record(:app, 1)
|
1572
|
+
# report_types = find_all("reportTypes", {phrase:"amazon"})
|
1573
|
+
# pools = find_all("loadBalancerPool", load_balancer_id)
|
1574
|
+
# @return Array of records
|
1575
|
+
def find_all(*args)
|
1576
|
+
#Morpheus::Logging::DarkPrinter.puts "find_all(#{args.join(', ')})" if Morpheus::Logging.debug?
|
1577
|
+
type, *request_args = args
|
1578
|
+
type = type.to_s.singularize.underscore
|
1579
|
+
list_key = respond_to?("#{type}_list_key", true) ? send("#{type}_list_key") : type.camelcase.pluralize
|
1580
|
+
json_response = find_all_json(*args)
|
1581
|
+
if !json_response.key?(list_key)
|
1582
|
+
# maybe just use the first key like this:
|
1583
|
+
# list_key = json_response.keys.find { |k| json_response[k].is_a?(Array) }
|
1584
|
+
# print_error(json_response) if Morpheus::Logging.debug?
|
1585
|
+
raise "API response is missing list property '#{list_key}'"
|
1586
|
+
end
|
1587
|
+
return json_response[list_key]
|
1588
|
+
end
|
1589
|
+
|
1590
|
+
# Load json response for a list of records by type
|
1591
|
+
# @return Hash of JSON data
|
1592
|
+
def find_all_json(*args)
|
1593
|
+
type, *request_args = args
|
1594
|
+
get_interface(type).list(*request_args)
|
1595
|
+
end
|
1596
|
+
|
1597
|
+
alias :find_all_records :find_all
|
1598
|
+
alias :find_all_records_json :find_all_json
|
1599
|
+
|
1600
|
+
# Load a single record (Hash) by type and id and optional parameters
|
1601
|
+
# Examples:
|
1602
|
+
# apps = find_record(:app)
|
1603
|
+
# report_types = find_record("reportTypes", {phrase:"amazon"})
|
1604
|
+
# pools = find_record("loadBalancerPool", balancer_id)
|
1605
|
+
# @return [Hash] of the object that was found or raises an exception if 404 not found encountered.
|
1606
|
+
def find_record(*args)
|
1607
|
+
#Morpheus::Logging::DarkPrinter.puts "find_record(#{args.join(', ')})" if Morpheus::Logging.debug?
|
1608
|
+
type, *request_args = args
|
1609
|
+
type = type.to_s.singularize.underscore
|
1610
|
+
object_key = respond_to?("#{type}_object_key", true) ? send("#{type}_object_key") : type.camelcase.singularize
|
1611
|
+
json_response = find_record_json(*args)
|
1612
|
+
if !json_response.key?(object_key)
|
1613
|
+
# maybe just use the first key like this:
|
1614
|
+
# object_key = json_response.keys.find { |k| json_response[k].is_a?(Hash) }
|
1615
|
+
# print_error(json_response) if Morpheus::Logging.debug?
|
1616
|
+
raise "API response is missing object property '#{object_key}'"
|
1617
|
+
end
|
1618
|
+
return json_response[object_key]
|
1619
|
+
end
|
1620
|
+
|
1621
|
+
# Load list of records by type and (optional) parameters
|
1622
|
+
# Examples:
|
1623
|
+
# apps = find_record(:app, 1)
|
1624
|
+
# report_types = find_record("reportType", 1)
|
1625
|
+
# pools = find_all("loadBalancerPool", load_balancer_id)
|
1626
|
+
def find_record_json(*args)
|
1627
|
+
#Morpheus::Logging::DarkPrinter.puts "find_record_json(#{args.join(', ')})" if Morpheus::Logging.debug?
|
1628
|
+
type, *request_args = args
|
1629
|
+
get_interface(type).get(*request_args)
|
1630
|
+
end
|
1631
|
+
|
1632
|
+
# Load json response for a list of records by type
|
1633
|
+
def get_interface(type)
|
1634
|
+
#Morpheus::Logging::DarkPrinter.puts "get_interface(#{type})" if Morpheus::Logging.debug?
|
1635
|
+
# todo: can probably just do @api_client ? @api_client.interface(interface_name) : nil
|
1636
|
+
type = type.to_s.singularize.underscore
|
1637
|
+
interface_name = "@#{type.pluralize}_interface"
|
1638
|
+
interface = nil
|
1639
|
+
if instance_variable_defined?(interface_name)
|
1640
|
+
interface = instance_variable_get(interface_name)
|
1641
|
+
if interface.nil?
|
1642
|
+
# Fix is to update connect() to do @apps_interface = @api_client.apps
|
1643
|
+
raise "API Interface #{interface_name} is nil"
|
1644
|
+
end
|
1645
|
+
else
|
1646
|
+
if @api_client.is_a?(Morpheus::APIClient)
|
1647
|
+
interface = @api_client.interface(type.pluralize)
|
1648
|
+
else
|
1649
|
+
raise "#{self.class} has not initalized @api_client"
|
1650
|
+
end
|
1651
|
+
end
|
1652
|
+
return interface
|
1653
|
+
end
|
1654
|
+
|
1407
1655
|
module ClassMethods
|
1408
1656
|
|
1409
1657
|
def prog_name
|
@@ -298,7 +298,7 @@ module Morpheus
|
|
298
298
|
every_command = cached_command_list
|
299
299
|
guess = command_name
|
300
300
|
suggestions = []
|
301
|
-
while guess.size >= 3
|
301
|
+
while suggestions.empty? && guess.size >= 3
|
302
302
|
plural_guess = guess.pluralize
|
303
303
|
if every_command.include?(guess)
|
304
304
|
suggestions << guess
|
@@ -270,7 +270,7 @@ EOT
|
|
270
270
|
out << "\n"
|
271
271
|
out << cyan
|
272
272
|
out << as_pretty_table(my_aliases, alias_columns, {:border_style => :thin}.merge(options))
|
273
|
-
out << format_results_pagination({size
|
273
|
+
out << format_results_pagination({'size' => my_aliases.size, 'total' => my_aliases.size.to_i})
|
274
274
|
out << reset
|
275
275
|
out << "\n"
|
276
276
|
end
|
@@ -10,9 +10,7 @@ class Morpheus::Cli::Apps
|
|
10
10
|
set_command_name :apps
|
11
11
|
set_command_description "View and manage apps."
|
12
12
|
register_subcommands :list, :count, :get, :view, :add, :update, :remove, :cancel_removal, :add_instance, :remove_instance, :logs, :security_groups, :apply_security_groups, :history
|
13
|
-
register_subcommands :
|
14
|
-
register_subcommands :apply
|
15
|
-
register_subcommands :refresh
|
13
|
+
register_subcommands :refresh, :apply
|
16
14
|
register_subcommands :stop, :start, :restart
|
17
15
|
register_subcommands :wiki, :update_wiki
|
18
16
|
#register_subcommands :firewall_disable, :firewall_enable
|
@@ -27,7 +25,7 @@ class Morpheus::Cli::Apps
|
|
27
25
|
def connect(opts)
|
28
26
|
@api_client = establish_remote_appliance_connection(opts)
|
29
27
|
@accounts_interface = @api_client.accounts
|
30
|
-
@
|
28
|
+
@account_users_interface = @api_client.account_users
|
31
29
|
@apps_interface = @api_client.apps
|
32
30
|
@blueprints_interface = @api_client.blueprints
|
33
31
|
@instance_types_interface = @api_client.instance_types
|
@@ -892,9 +890,7 @@ This is only supported by certain types of apps.
|
|
892
890
|
EOT
|
893
891
|
end
|
894
892
|
optparse.parse!(args)
|
895
|
-
|
896
|
-
raise_command_error "wrong number of arguments, expected 1 and got (#{args.count}) #{args.join(', ')}\n#{optparse}"
|
897
|
-
end
|
893
|
+
verify_args!(args:args, optparse:optparse, count:1)
|
898
894
|
connect(options)
|
899
895
|
|
900
896
|
begin
|
@@ -919,65 +915,11 @@ EOT
|
|
919
915
|
return
|
920
916
|
end
|
921
917
|
json_response = @apps_interface.refresh(app["id"], params, payload)
|
922
|
-
|
923
|
-
|
924
|
-
|
925
|
-
return get([app['id']] + (options[:remote] ? ["-r",options[:remote]] : []))
|
926
|
-
rescue RestClient::Exception => e
|
927
|
-
print_rest_exception(e, options)
|
928
|
-
exit 1
|
929
|
-
end
|
930
|
-
end
|
931
|
-
|
932
|
-
def prepare_apply(args)
|
933
|
-
params, payload, options = {}, {}, {}
|
934
|
-
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
935
|
-
opts.banner = subcommand_usage("[app] [options]")
|
936
|
-
build_standard_update_options(opts, options, [:auto_confirm])
|
937
|
-
opts.footer = <<-EOT
|
938
|
-
Prepare to apply an app.
|
939
|
-
[app] is required. This is the name or id of an app.
|
940
|
-
Template parameter values can be applied with -O templateParameter.foo=bar
|
941
|
-
This only prints the app configuration that would be applied.
|
942
|
-
It does not make any updates.
|
943
|
-
This is only supported by certain types of apps.
|
944
|
-
EOT
|
945
|
-
end
|
946
|
-
optparse.parse!(args)
|
947
|
-
if args.count != 1
|
948
|
-
raise_command_error "wrong number of arguments, expected 1 and got (#{args.count}) #{args.join(', ')}\n#{optparse}"
|
949
|
-
end
|
950
|
-
connect(options)
|
951
|
-
|
952
|
-
begin
|
953
|
-
app = find_app_by_name_or_id(args[0])
|
954
|
-
return 1 if app.nil?
|
955
|
-
# construct request
|
956
|
-
params.merge!(parse_query_options(options))
|
957
|
-
payload = {}
|
958
|
-
if options[:payload]
|
959
|
-
payload = options[:payload]
|
960
|
-
payload.deep_merge!(parse_passed_options(options))
|
961
|
-
else
|
962
|
-
payload.deep_merge!(parse_passed_options(options))
|
963
|
-
# raise_command_error "Specify at least one option to update.\n#{optparse}" if payload.empty?
|
918
|
+
render_response(json_response, options) do
|
919
|
+
print_green_success "Refreshing app #{app['name']}"
|
920
|
+
# return _get(app['id'], options)
|
964
921
|
end
|
965
|
-
|
966
|
-
if options[:dry_run]
|
967
|
-
print_dry_run @apps_interface.dry.prepare_apply(app["id"], params, payload)
|
968
|
-
return
|
969
|
-
end
|
970
|
-
json_response = @apps_interface.prepare_apply(app["id"], params, payload)
|
971
|
-
render_result = render_with_format(json_response, options)
|
972
|
-
return 0 if render_result
|
973
|
-
# print_green_success "Prepared to apply app: #{app['name']}"
|
974
|
-
print_h1 "Prepared App: #{app['name']}"
|
975
|
-
app_config = json_response['data']
|
976
|
-
# app_config = json_response if app_config.nil?
|
977
|
-
puts as_yaml(app_config, options)
|
978
|
-
#return get([app['id']] + (options[:remote] ? ["-r",options[:remote]] : []))
|
979
|
-
print "\n", reset
|
980
|
-
return 0
|
922
|
+
return 0, nil
|
981
923
|
rescue RestClient::Exception => e
|
982
924
|
print_rest_exception(e, options)
|
983
925
|
exit 1
|
@@ -992,17 +934,11 @@ EOT
|
|
992
934
|
opts.footer = <<-EOT
|
993
935
|
Apply an app.
|
994
936
|
[app] is required. This is the name or id of an app.
|
995
|
-
|
996
|
-
This is a way to apply an app with new configuration parameters to an app.
|
997
|
-
This prints the app configuration that would be applied.
|
998
|
-
It does not make any updates.
|
999
|
-
This is only supported by certain types of apps.
|
937
|
+
This is only supported by certain types of apps such as terraform.
|
1000
938
|
EOT
|
1001
939
|
end
|
1002
940
|
optparse.parse!(args)
|
1003
|
-
|
1004
|
-
raise_command_error "wrong number of arguments, expected 1 and got (#{args.count}) #{args.join(', ')}\n#{optparse}"
|
1005
|
-
end
|
941
|
+
verify_args!(args:args, optparse:optparse, count:1)
|
1006
942
|
connect(options)
|
1007
943
|
|
1008
944
|
begin
|
@@ -1027,11 +963,11 @@ EOT
|
|
1027
963
|
return
|
1028
964
|
end
|
1029
965
|
json_response = @apps_interface.apply(app["id"], params, payload)
|
1030
|
-
|
1031
|
-
|
1032
|
-
|
1033
|
-
|
1034
|
-
return 0
|
966
|
+
render_response(json_response, options) do
|
967
|
+
print_green_success "Applying app #{app['name']}"
|
968
|
+
# return _get(app['id'], options)
|
969
|
+
end
|
970
|
+
return 0, nil
|
1035
971
|
rescue RestClient::Exception => e
|
1036
972
|
print_rest_exception(e, options)
|
1037
973
|
exit 1
|
@@ -0,0 +1,188 @@
|
|
1
|
+
require 'morpheus/cli/cli_command'
|
2
|
+
|
3
|
+
class Morpheus::Cli::Audit
|
4
|
+
include Morpheus::Cli::CliCommand
|
5
|
+
include Morpheus::Cli::LogsHelper
|
6
|
+
include Morpheus::Cli::RestCommand
|
7
|
+
include Morpheus::Cli::OptionSourceHelper
|
8
|
+
|
9
|
+
set_command_description "View audit log records."
|
10
|
+
set_command_name :'audit'
|
11
|
+
register_subcommands :list, :get
|
12
|
+
|
13
|
+
# audit is not published yet
|
14
|
+
set_command_hidden
|
15
|
+
|
16
|
+
# RestCommand settings
|
17
|
+
|
18
|
+
# interfaces
|
19
|
+
register_interfaces :audit
|
20
|
+
set_rest_interface_name :audit
|
21
|
+
|
22
|
+
# resource name is "Audit Log"
|
23
|
+
set_rest_name :audit_log
|
24
|
+
|
25
|
+
# display argument as [id] instead of [audit log]
|
26
|
+
set_rest_has_name false
|
27
|
+
set_rest_arg "id"
|
28
|
+
|
29
|
+
# def connect(opts)
|
30
|
+
# @api_client = establish_remote_appliance_connection(opts)
|
31
|
+
# @audit_interface = @api_client.audit # @api_client.rest("audit")
|
32
|
+
# end
|
33
|
+
|
34
|
+
# def handle(args)
|
35
|
+
# handle_subcommand(args)
|
36
|
+
# end
|
37
|
+
|
38
|
+
def list(args)
|
39
|
+
options = {}
|
40
|
+
params = {}
|
41
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
42
|
+
opts.banner = subcommand_usage("[search]")
|
43
|
+
opts.on('--user USER', String, "Filter by User Username or ID") do |val|
|
44
|
+
params['user'] = params['user'] ? [params['user'], val].flatten : [val]
|
45
|
+
end
|
46
|
+
opts.on('--level VALUE', String, "Log Level. DEBUG|INFO|WARN|ERROR") do |val|
|
47
|
+
params['level'] = params['level'] ? [params['level'], val].flatten : [val]
|
48
|
+
end
|
49
|
+
opts.on('--start TIMESTAMP','--start TIMESTAMP', "Start date timestamp in standard iso8601 format.") do |val|
|
50
|
+
params['startDate'] = val # parse_time(val).utc.iso8601
|
51
|
+
end
|
52
|
+
opts.on('--end TIMESTAMP','--end TIMESTAMP', "End date timestamp in standard iso8601 format.") do |val|
|
53
|
+
params['endDate'] = val # parse_time(val).utc.iso8601
|
54
|
+
end
|
55
|
+
build_standard_list_options(opts, options)
|
56
|
+
opts.footer = "List audit logs records."
|
57
|
+
end
|
58
|
+
optparse.parse!(args)
|
59
|
+
if args.count > 0
|
60
|
+
options[:phrase] = args.join(" ")
|
61
|
+
end
|
62
|
+
connect(options)
|
63
|
+
params.merge!(parse_list_options(options))
|
64
|
+
# parse --user id,name
|
65
|
+
if params['user']
|
66
|
+
user_ids = parse_user_id_list(params['user'])
|
67
|
+
return 1 if user_ids.nil?
|
68
|
+
params['user'] = user_ids
|
69
|
+
end
|
70
|
+
# api works with level=INFO|WARN
|
71
|
+
if params['level']
|
72
|
+
params['level'] = [params['level']].flatten.collect {|it| it.to_s.upcase }.join('|')
|
73
|
+
end
|
74
|
+
# could find_by_name_or_id for params['servers'] and params['containers']
|
75
|
+
@audit_interface.setopts(options)
|
76
|
+
if options[:dry_run]
|
77
|
+
print_dry_run @audit_interface.dry.list(params)
|
78
|
+
return
|
79
|
+
end
|
80
|
+
json_response = @audit_interface.list(params)
|
81
|
+
|
82
|
+
render_response(json_response, options, rest_list_key) do
|
83
|
+
records = json_response[rest_list_key]
|
84
|
+
print_h1 "Morpheus Audit Log", parse_list_subtitles(options), options
|
85
|
+
if records.nil? || records.empty?
|
86
|
+
print cyan,"No #{rest_label_plural.downcase} found.",reset,"\n"
|
87
|
+
else
|
88
|
+
print as_pretty_table(records, rest_list_column_definitions(options).upcase_keys!, options)
|
89
|
+
print_results_pagination(json_response) if json_response['meta']
|
90
|
+
end
|
91
|
+
print reset,"\n"
|
92
|
+
end
|
93
|
+
return 0, nil
|
94
|
+
end
|
95
|
+
|
96
|
+
protected
|
97
|
+
|
98
|
+
# custom rendering to print Message below description list
|
99
|
+
def render_response_for_get(json_response, options)
|
100
|
+
render_response(json_response, options, rest_object_key) do
|
101
|
+
record = json_response[rest_object_key]
|
102
|
+
print_h1 rest_label, [], options
|
103
|
+
print cyan
|
104
|
+
print_description_list(rest_column_definitions(options), record, options)
|
105
|
+
# show log message settings...
|
106
|
+
print_h2 "Message", options
|
107
|
+
print cyan
|
108
|
+
puts record['message']
|
109
|
+
print reset,"\n"
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def audit_log_object_key
|
114
|
+
"auditLog"
|
115
|
+
end
|
116
|
+
|
117
|
+
def audit_log_list_key
|
118
|
+
"auditLogs"
|
119
|
+
end
|
120
|
+
|
121
|
+
def audit_log_list_column_definitions(options={})
|
122
|
+
{
|
123
|
+
"ID" => 'id',
|
124
|
+
"Level" => lambda {|it| format_log_level(it['level']) },
|
125
|
+
"Message" => {display_method:'message', max_width: (options[:wrap] ? nil : 75)},
|
126
|
+
"Event Type" => 'eventType',
|
127
|
+
"Object" => lambda {|it| "#{it['objectClass']} #{it['objectId']}".strip },
|
128
|
+
# "Object Type" => 'objectClass',
|
129
|
+
# "Object ID" => 'objectId',
|
130
|
+
"User" => lambda {|it|
|
131
|
+
if it['actualUser'] && it['user'] && it['actualUser']['username'] != it['user']['username']
|
132
|
+
it['user']['username'] + '(' + it['actualUser']['username'].to_s + ')'
|
133
|
+
elsif it['user']
|
134
|
+
it['user']['username']
|
135
|
+
else
|
136
|
+
# system or deleted user maybe?
|
137
|
+
end
|
138
|
+
},
|
139
|
+
# "Tenant" => lambda {|it| it['account'] ? it['account']['name'] : '' },
|
140
|
+
"Created" => lambda {|it| format_local_dt(it['dateCreated']) },
|
141
|
+
}
|
142
|
+
end
|
143
|
+
|
144
|
+
def audit_log_column_definitions(options={})
|
145
|
+
{
|
146
|
+
"ID" => 'id',
|
147
|
+
"Level" => lambda {|it| format_log_level(it['level']) },
|
148
|
+
#"Message" => 'message',
|
149
|
+
"Event Type" => 'eventType',
|
150
|
+
"Object Type" => 'objectClass',
|
151
|
+
"Object ID" => 'objectId',
|
152
|
+
"User" => lambda {|it|
|
153
|
+
if it['actualUser'] && it['user'] && it['actualUser']['username'] != it['user']['username']
|
154
|
+
it['user']['username'] + '(' + it['actualUser']['username'].to_s + ')'
|
155
|
+
elsif it['user']
|
156
|
+
it['user']['username']
|
157
|
+
else
|
158
|
+
# system or deleted user maybe?
|
159
|
+
end
|
160
|
+
},
|
161
|
+
# "Tenant" => lambda {|it| it['account'] ? it['account']['name'] : '' },
|
162
|
+
"Created" => lambda {|it| format_local_dt(it['dateCreated']) },
|
163
|
+
}
|
164
|
+
end
|
165
|
+
|
166
|
+
def find_audit_log_by_name_or_id(val)
|
167
|
+
return find_audit_log_by_id(val)
|
168
|
+
end
|
169
|
+
|
170
|
+
def find_audit_log_by_id(id)
|
171
|
+
begin
|
172
|
+
json_response = @audit_interface.get(id)
|
173
|
+
return json_response[audit_log_object_key]
|
174
|
+
rescue RestClient::Exception => e
|
175
|
+
if e.response && e.response.code == 404
|
176
|
+
print_red_alert "Audit Log not found by id #{id}"
|
177
|
+
return nil
|
178
|
+
else
|
179
|
+
raise e
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
def find_audit_log_by_name(name)
|
185
|
+
raise_command_error "finding audit log by name not supported"
|
186
|
+
end
|
187
|
+
|
188
|
+
end
|
@@ -36,7 +36,7 @@ class Morpheus::Cli::BlueprintsCommand
|
|
36
36
|
@options_interface = @api_client.options
|
37
37
|
@active_group_id = Morpheus::Cli::Groups.active_groups[@appliance_name]
|
38
38
|
@clouds_interface = @api_client.clouds
|
39
|
-
@
|
39
|
+
@account_users_interface = @api_client.account_users
|
40
40
|
@library_layouts_interface = @api_client.library_layouts
|
41
41
|
end
|
42
42
|
|