morpheus-cli 4.2.8 → 4.2.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (88) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +1 -1
  3. data/lib/morpheus/api.rb +1 -1
  4. data/lib/morpheus/api/activity_interface.rb +9 -0
  5. data/lib/morpheus/api/api_client.rb +83 -27
  6. data/lib/morpheus/api/apps_interface.rb +21 -0
  7. data/lib/morpheus/api/dashboard_interface.rb +5 -21
  8. data/lib/morpheus/api/instances_interface.rb +3 -10
  9. data/lib/morpheus/api/invoice_line_items_interface.rb +14 -0
  10. data/lib/morpheus/api/invoices_interface.rb +7 -12
  11. data/lib/morpheus/api/library_layouts_interface.rb +8 -0
  12. data/lib/morpheus/api/ping_interface.rb +20 -0
  13. data/lib/morpheus/api/projects_interface.rb +33 -0
  14. data/lib/morpheus/api/setup_interface.rb +19 -36
  15. data/lib/morpheus/api/user_settings_interface.rb +0 -6
  16. data/lib/morpheus/api/whoami_interface.rb +4 -8
  17. data/lib/morpheus/benchmarking.rb +16 -26
  18. data/lib/morpheus/cli.rb +10 -5
  19. data/lib/morpheus/cli/access_token_command.rb +5 -8
  20. data/lib/morpheus/cli/activity_command.rb +146 -0
  21. data/lib/morpheus/cli/apps.rb +312 -121
  22. data/lib/morpheus/cli/archives_command.rb +1 -1
  23. data/lib/morpheus/cli/auth_command.rb +4 -11
  24. data/lib/morpheus/cli/blueprints_command.rb +196 -137
  25. data/lib/morpheus/cli/change_password_command.rb +1 -1
  26. data/lib/morpheus/cli/cli_command.rb +225 -72
  27. data/lib/morpheus/cli/cli_registry.rb +2 -2
  28. data/lib/morpheus/cli/cloud_datastores_command.rb +1 -1
  29. data/lib/morpheus/cli/clouds.rb +5 -20
  30. data/lib/morpheus/cli/clusters.rb +4 -28
  31. data/lib/morpheus/cli/commands/standard/alias_command.rb +2 -9
  32. data/lib/morpheus/cli/commands/standard/benchmark_command.rb +2 -0
  33. data/lib/morpheus/cli/commands/standard/curl_command.rb +2 -3
  34. data/lib/morpheus/cli/commands/standard/history_command.rb +3 -6
  35. data/lib/morpheus/cli/commands/standard/man_command.rb +10 -7
  36. data/lib/morpheus/cli/commands/standard/ssl_verification_command.rb +10 -9
  37. data/lib/morpheus/cli/containers_command.rb +3 -3
  38. data/lib/morpheus/cli/credentials.rb +13 -16
  39. data/lib/morpheus/cli/error_handler.rb +18 -12
  40. data/lib/morpheus/cli/errors.rb +45 -0
  41. data/lib/morpheus/cli/execute_schedules_command.rb +1 -1
  42. data/lib/morpheus/cli/execution_request_command.rb +4 -4
  43. data/lib/morpheus/cli/groups.rb +84 -132
  44. data/lib/morpheus/cli/hosts.rb +6 -16
  45. data/lib/morpheus/cli/instances.rb +100 -183
  46. data/lib/morpheus/cli/invoices_command.rb +505 -71
  47. data/lib/morpheus/cli/library_layouts_command.rb +254 -166
  48. data/lib/morpheus/cli/library_option_lists_command.rb +0 -87
  49. data/lib/morpheus/cli/library_option_types_command.rb +0 -96
  50. data/lib/morpheus/cli/license.rb +3 -0
  51. data/lib/morpheus/cli/login.rb +17 -37
  52. data/lib/morpheus/cli/logout.rb +9 -5
  53. data/lib/morpheus/cli/mixins/accounts_helper.rb +83 -7
  54. data/lib/morpheus/cli/mixins/operations_helper.rb +41 -0
  55. data/lib/morpheus/cli/mixins/option_source_helper.rb +255 -0
  56. data/lib/morpheus/cli/mixins/print_helper.rb +18 -4
  57. data/lib/morpheus/cli/mixins/provisioning_helper.rb +222 -13
  58. data/lib/morpheus/cli/mixins/remote_helper.rb +139 -0
  59. data/lib/morpheus/cli/monitoring_checks_command.rb +11 -3
  60. data/lib/morpheus/cli/network_groups_command.rb +8 -2
  61. data/lib/morpheus/cli/option_types.rb +1 -1
  62. data/lib/morpheus/cli/ping.rb +252 -0
  63. data/lib/morpheus/cli/price_sets_command.rb +16 -27
  64. data/lib/morpheus/cli/prices_command.rb +34 -27
  65. data/lib/morpheus/cli/processes_command.rb +81 -7
  66. data/lib/morpheus/cli/projects_command.rb +607 -0
  67. data/lib/morpheus/cli/recent_activity_command.rb +87 -65
  68. data/lib/morpheus/cli/remote.rb +965 -974
  69. data/lib/morpheus/cli/reports_command.rb +3 -15
  70. data/lib/morpheus/cli/roles.rb +8 -31
  71. data/lib/morpheus/cli/service_plans_command.rb +25 -31
  72. data/lib/morpheus/cli/setup.rb +392 -0
  73. data/lib/morpheus/cli/shell.rb +144 -56
  74. data/lib/morpheus/cli/subnets_command.rb +71 -11
  75. data/lib/morpheus/cli/tasks.rb +3 -3
  76. data/lib/morpheus/cli/user_sources_command.rb +4 -4
  77. data/lib/morpheus/cli/users.rb +135 -109
  78. data/lib/morpheus/cli/version.rb +1 -1
  79. data/lib/morpheus/cli/whitelabel_settings_command.rb +7 -7
  80. data/lib/morpheus/cli/whoami.rb +90 -129
  81. data/lib/morpheus/cli/wiki_command.rb +2 -14
  82. data/lib/morpheus/ext/rest_client.rb +36 -0
  83. data/lib/morpheus/formatters.rb +42 -5
  84. data/lib/morpheus/rest_client.rb +0 -10
  85. data/lib/morpheus/terminal.rb +41 -1
  86. data/lib/morpheus/util.rb +24 -0
  87. metadata +16 -3
  88. data/lib/morpheus/cli/command_error.rb +0 -22
@@ -39,30 +39,20 @@ class Morpheus::Cli::PriceSetsCommand
39
39
  end
40
40
  optparse.parse!(args)
41
41
  connect(options)
42
- if args.count != 0
43
- raise_command_error "wrong number of arguments, expected 0 and got (#{args.count}) #{args}\n#{optparse}"
44
- return 1
42
+ params.merge!(parse_list_options(options))
43
+ params['phrase'] = args.join(' ') if args.count > 0 && params['phrase'].nil? # pass args as phrase, every list command should do this
44
+ @price_sets_interface.setopts(options)
45
+ if options[:dry_run]
46
+ print_dry_run @price_sets_interface.dry.list(params)
47
+ return
45
48
  end
46
-
47
- begin
48
- params.merge!(parse_list_options(options))
49
-
50
- @price_sets_interface.setopts(options)
51
- if options[:dry_run]
52
- print_dry_run @price_sets_interface.dry.list(params)
53
- return
54
- end
55
- json_response = @price_sets_interface.list(params)
56
-
57
- render_result = render_with_format(json_response, options, 'priceSets')
58
- return 0 if render_result
59
-
49
+ json_response = @price_sets_interface.list(params)
50
+ price_sets = json_response['priceSets']
51
+ render_response(json_response, options, 'priceSets') do
60
52
  title = "Morpheus Price Sets"
61
53
  subtitles = []
62
54
  subtitles += parse_list_subtitles(options)
63
55
  print_h1 title, subtitles
64
-
65
- price_sets = json_response['priceSets']
66
56
  if price_sets.empty?
67
57
  print cyan,"No price sets found.",reset,"\n"
68
58
  else
@@ -71,25 +61,24 @@ class Morpheus::Cli::PriceSetsCommand
71
61
  id: (it['active'] ? cyan : yellow) + it['id'].to_s,
72
62
  name: it['name'],
73
63
  active: format_boolean(it['active']),
74
- price_unit: it['priceUnit'],
64
+ priceUnit: it['priceUnit'],
75
65
  type: price_set_type_label(it['type']),
76
66
  price_count: it['prices'].count.to_s + cyan
77
67
  }
78
68
  end
79
69
  columns = [
80
- :id, :name, :active, :price_unit, :type, '# of prices' => :price_count
70
+ :id, :name, :active, :priceUnit, :type, '# OF PRICES' => :price_count
81
71
  ]
82
72
  columns.delete(:active) if !params['includeInactive']
83
-
84
73
  print as_pretty_table(rows, columns, options)
85
74
  print_results_pagination(json_response)
86
- print reset,"\n"
87
75
  end
88
76
  print reset,"\n"
89
- return 0
90
- rescue RestClient::Exception => e
91
- print_rest_exception(e, options)
92
- exit 1
77
+ end
78
+ if price_sets.empty?
79
+ return 1, "0 price sets found"
80
+ else
81
+ return 0, nil
93
82
  end
94
83
  end
95
84
 
@@ -30,35 +30,40 @@ class Morpheus::Cli::PricesCommand
30
30
  opts.on('-i', '--include-inactive [on|off]', String, "Can be used to enable / disable inactive filter. Default is on") do |val|
31
31
  params['includeInactive'] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == '1' || val.to_s == ''
32
32
  end
33
+ opts.on('--platform PLATFORM', Array, "Filter by platform eg. linux, windows") do |val|
34
+ params['platform'] = val.collect {|it| it.to_s.strip.empty? ? nil : it.to_s.strip }.compact
35
+ end
36
+ opts.on('--price-unit UNIT', Array, "Filter by priceUnit eg. hour, month") do |val|
37
+ params['priceUnit'] = val.collect {|it| it.to_s.strip.empty? ? nil : it.to_s.strip }.compact
38
+ end
39
+ opts.on('--currency CURRENCY', Array, "Filter by currency eg. usd") do |val|
40
+ params['currency'] = val.collect {|it| it.to_s.strip.empty? ? nil : it.to_s.strip }.compact
41
+ end
42
+ opts.on('--price-type TYPE', Array, "Filter by priceType eg. fixed,platform,software,compute,storage,datastore,memory,cores,cpu") do |val|
43
+ params['priceType'] = val.collect {|it| it.to_s.strip.empty? ? nil : it.to_s.strip }.compact
44
+ end
33
45
  build_common_options(opts, options, [:list, :query, :json, :yaml, :csv, :fields, :dry_run, :remote])
34
46
  opts.footer = "List prices."
35
47
  end
36
48
  optparse.parse!(args)
49
+ #verify_args!(args:args, optparse:optparse, count:0)
37
50
  connect(options)
38
- if args.count != 0
39
- raise_command_error "wrong number of arguments, expected 0 and got (#{args.count}) #{args}\n#{optparse}"
40
- return 1
51
+
52
+ params.merge!(parse_list_options(options))
53
+ params['phrase'] = args.join(' ') if args.count > 0 && params['phrase'].nil? # pass args as phrase, every list command should do this
54
+ load_whoami()
55
+ @prices_interface.setopts(options)
56
+ if options[:dry_run]
57
+ print_dry_run @prices_interface.dry.list(params)
58
+ return
41
59
  end
42
-
43
- begin
44
- params.merge!(parse_list_options(options))
45
-
46
- @prices_interface.setopts(options)
47
- if options[:dry_run]
48
- print_dry_run @prices_interface.dry.list(params)
49
- return
50
- end
51
- json_response = @prices_interface.list(params)
52
-
53
- render_result = render_with_format(json_response, options, 'prices')
54
- return 0 if render_result
55
-
60
+ json_response = @prices_interface.list(params)
61
+ prices = json_response['prices']
62
+ render_response(json_response, options, 'prices') do
56
63
  title = "Morpheus Prices"
57
64
  subtitles = []
58
65
  subtitles += parse_list_subtitles(options)
59
66
  print_h1 title, subtitles
60
-
61
- prices = json_response['prices']
62
67
  if prices.empty?
63
68
  print yellow,"No prices found.",reset,"\n"
64
69
  else
@@ -77,20 +82,17 @@ class Morpheus::Cli::PricesCommand
77
82
  }
78
83
  end
79
84
  columns = [
80
- :id, :name, :active, :priceType, :tenant, :priceUnit, :priceAdjustment, :cost, :markup, :price
85
+ :id, :name, :active, {'PRICE TYPE' => :priceType}, :tenant, {'PRICE UNIT' => :priceUnit}, {'PRICE ADJUSTMENT' => :priceAdjustment}, :cost, :markup, :price
81
86
  ]
82
87
  columns.delete(:active) if !params['includeInactive']
83
88
 
84
89
  print as_pretty_table(rows, columns, options)
85
90
  print_results_pagination(json_response)
86
- print reset,"\n"
87
91
  end
88
92
  print reset,"\n"
89
- return 0
90
- rescue RestClient::Exception => e
91
- print_rest_exception(e, options)
92
- exit 1
93
93
  end
94
+ return 1, "0 prices found" if prices.empty?
95
+ return 0, nil
94
96
  end
95
97
 
96
98
  def get(args)
@@ -552,7 +554,12 @@ class Morpheus::Cli::PricesCommand
552
554
  end
553
555
 
554
556
  def currency_sym(currency)
555
- Money::Currency.new((currency || 'usd').to_sym).symbol
557
+ begin
558
+ Money::Currency.new((currency.to_s.empty? ? 'usd' : currency).to_sym).symbol
559
+ rescue
560
+ # sometimes '' is getting passed in here, so figure that out...
561
+ Money::Currency.new(:usd).symbol
562
+ end
556
563
  end
557
564
 
558
565
  def price_prefix(price)
@@ -570,7 +577,7 @@ class Morpheus::Cli::PricesCommand
570
577
  end
571
578
 
572
579
  def price_type_label(type)
573
- price_types[type] || type.capitalize
580
+ price_types[type] || type.to_s.capitalize
574
581
  end
575
582
 
576
583
  def price_types
@@ -4,6 +4,9 @@ require 'morpheus/cli/mixins/processes_helper'
4
4
  class Morpheus::Cli::Processes
5
5
  include Morpheus::Cli::CliCommand
6
6
  include Morpheus::Cli::ProcessesHelper
7
+ include Morpheus::Cli::ProvisioningHelper
8
+ include Morpheus::Cli::InfrastructureHelper
9
+ include Morpheus::Cli::OptionSourceHelper
7
10
 
8
11
  set_command_name :'process'
9
12
 
@@ -16,9 +19,11 @@ class Morpheus::Cli::Processes
16
19
  #@appliance_name, @appliance_url = Morpheus::Cli::Remote.active_appliance
17
20
  end
18
21
 
19
- def connect(opts)
20
- @api_client = establish_remote_appliance_connection(opts)
22
+ def connect(options)
23
+ @api_client = establish_remote_appliance_connection(options)
21
24
  @processes_interface = @api_client.processes
25
+ #@instances_interface = @api_client.instances
26
+ @clouds_interface = @api_client.clouds
22
27
  end
23
28
 
24
29
  def handle(args)
@@ -42,21 +47,29 @@ class Morpheus::Cli::Processes
42
47
  options[:show_output] = true
43
48
  options[:details] = true
44
49
  end
45
- opts.on('--app ID', String, "Limit results to specific app(s).") do |val|
50
+ opts.on('--app APP', String, "Limit results to specific app(s).") do |val|
46
51
  params['appIds'] = val.split(',').collect {|it| it.to_s.strip }.reject { |it| it.empty? }
47
52
  end
48
- opts.on('--instance ID', String, "Limit results to specific instance(s).") do |val|
53
+ opts.on('--instance INSTANCE', String, "Limit results to specific instance(s).") do |val|
49
54
  params['instanceIds'] = val.split(',').collect {|it| it.to_s.strip }.reject { |it| it.empty? }
50
55
  end
51
- opts.on('--container ID', String, "Limit results to specific container(s).") do |val|
56
+ opts.on('--container CONTAINER', String, "Limit results to specific container(s).") do |val|
52
57
  params['containerIds'] = val.split(',').collect {|it| it.to_s.strip }.reject { |it| it.empty? }
53
58
  end
54
- opts.on('--host ID', String, "Limit results to specific host(s).") do |val|
59
+ opts.on('--host HOST', String, "Limit results to specific host(s).") do |val|
55
60
  params['serverIds'] = val.split(',').collect {|it| it.to_s.strip }.reject { |it| it.empty? }
56
61
  end
57
- opts.on('--cloud ID', String, "Limit results to specific cloud(s).") do |val|
62
+ opts.on('--server HOST', String, "Limit results to specific servers(s).") do |val|
63
+ params['serverIds'] = val.split(',').collect {|it| it.to_s.strip }.reject { |it| it.empty? }
64
+ end
65
+ opts.add_hidden_option('--server')
66
+ opts.on('--cloud CLOUD', String, "Limit results to specific cloud(s).") do |val|
58
67
  params['zoneIds'] = val.split(',').collect {|it| it.to_s.strip }.reject { |it| it.empty? }
59
68
  end
69
+ opts.on('--user USER', String, "Limit results to user(s).") do |val|
70
+ #params['userId'] = val.split(',').collect {|it| it.to_s.strip }.reject { |it| it.empty? }
71
+ options[:user] = val
72
+ end
60
73
  build_common_options(opts, options, [:list, :query, :json, :yaml, :csv, :fields, :dry_run, :remote])
61
74
  opts.footer = "List historical processes."
62
75
  end
@@ -69,6 +82,67 @@ class Morpheus::Cli::Processes
69
82
  connect(options)
70
83
  begin
71
84
  params.merge!(parse_list_options(options))
85
+ if params['instanceIds']
86
+ params['instanceIds'] = params['instanceIds'].collect do |instance_id|
87
+ if instance_id.to_s =~ /\A\d{1,}\Z/
88
+ # just allow instance IDs
89
+ instance_id.to_i
90
+ else
91
+ instance = find_instance_by_name_or_id(instance_id)
92
+ if instance.nil?
93
+ return 1, "instance not found for '#{instance_id}'" # never happens because find exits
94
+ end
95
+ instance['id']
96
+ end
97
+ end
98
+ end
99
+ if params['serverIds']
100
+ params['serverIds'] = params['serverIds'].collect do |server_id|
101
+ if server_id.to_s =~ /\A\d{1,}\Z/
102
+ # just allow server IDs
103
+ server_id.to_i
104
+ else
105
+ server = find_server_by_name_or_id(server_id)
106
+ if server.nil?
107
+ return 1, "server not found for '#{server_id}'" # never happens because find exits
108
+ end
109
+ server['id']
110
+ end
111
+ end
112
+ end
113
+ if params['appIds']
114
+ params['appIds'] = params['appIds'].collect do |app_id|
115
+ if app_id.to_s =~ /\A\d{1,}\Z/
116
+ # just allow app IDs
117
+ app_id.to_i
118
+ else
119
+ app = find_app_by_name_or_id(app_id)
120
+ if app.nil?
121
+ return 1, "app not found for '#{app_id}'" # never happens because find exits
122
+ end
123
+ app['id']
124
+ end
125
+ end
126
+ end
127
+ if params['zoneIds']
128
+ params['zoneIds'] = params['zoneIds'].collect do |zone_id|
129
+ if zone_id.to_s =~ /\A\d{1,}\Z/
130
+ # just allow zone IDs
131
+ zone_id.to_i
132
+ else
133
+ zone = find_cloud_by_name_or_id(zone_id)
134
+ if zone.nil?
135
+ return 1, "cloud not found for '#{zone_id}'" # never happens because find exits
136
+ end
137
+ zone['id']
138
+ end
139
+ end
140
+ end
141
+ if options[:user]
142
+ user = find_available_user_option(options[:user])
143
+ return 1, "user not found by '#{options[:user]}'" if user.nil?
144
+ params['userId'] = user['id']
145
+ end
72
146
  @processes_interface.setopts(options)
73
147
  if options[:dry_run]
74
148
  print_dry_run @processes_interface.dry.list(params)
@@ -0,0 +1,607 @@
1
+ require 'morpheus/cli/cli_command'
2
+
3
+ class Morpheus::Cli::Projects
4
+ include Morpheus::Cli::CliCommand
5
+ include Morpheus::Cli::ProvisioningHelper
6
+ include Morpheus::Cli::OptionSourceHelper
7
+ # include Morpheus::Cli::InfrastructureHelper
8
+ # include Morpheus::Cli::AccountsHelper
9
+
10
+ register_subcommands :list, :get, :add, :update, :remove,
11
+ # :add_tags, :remove_tags,
12
+ #:add_instance, :remove_instance
13
+ #:add_host, :remove_host
14
+
15
+ def connect(opts)
16
+ @api_client = establish_remote_appliance_connection(opts)
17
+ @projects_interface = @api_client.projects
18
+ @instances_interface = @api_client.instances
19
+ @servers_interface = @api_client.servers
20
+ @clouds_interface = @api_client.clouds
21
+ end
22
+
23
+ def handle(args)
24
+ handle_subcommand(args)
25
+ end
26
+
27
+ def list(args)
28
+ params = {}
29
+ options = {}
30
+ instance_ids, server_ids, cloud_ids = nil, nil, nil
31
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
32
+ opts.banner = subcommand_usage()
33
+ opts.on('--instances [LIST]', Array, "Filter by Instance, comma separated list of instance names or IDs.") do |list|
34
+ instance_ids = list.collect {|it| it.to_s.strip.empty? ? nil : it.to_s.strip }.compact.uniq
35
+ end
36
+ opts.on('--servers [LIST]', Array, "Filter by Server, comma separated list of server (host) names or IDs.") do |list|
37
+ server_ids = list.collect {|it| it.to_s.strip.empty? ? nil : it.to_s.strip }.compact.uniq
38
+ end
39
+ opts.on('--clouds [LIST]', Array, "Filter by Cloud, comma separated list of cloud (zone) names or IDs.") do |list|
40
+ cloud_ids = list.collect {|it| it.to_s.strip.empty? ? nil : it.to_s.strip }.compact.uniq
41
+ end
42
+ # opts.on('--owner [LIST]', Array, "Owner, comma separated list of usernames or IDs.") do |list|
43
+ # params['ownerId'] = list.collect {|it| it.to_s.strip.empty? ? nil : it.to_s.strip }.compact.uniq
44
+ # end
45
+ build_standard_get_options(opts, options)
46
+ opts.footer = <<-EOT
47
+ List projects.
48
+ EOT
49
+ end
50
+ optparse.parse!(args)
51
+ #verify_args!(args:args, optparse:optparse, count:0)
52
+ connect(options)
53
+ options[:phrase] = args.join(' ') if args.count > 0 && params[:phrase].nil? # pass args as phrase, every list command should do this
54
+ params.merge!(parse_list_options(options))
55
+ params['instanceId'] = parse_instance_id_list(instance_ids) if instance_ids
56
+ # todo server and cloud, missing parse_server_id_list() too
57
+ params['serverId'] = parse_server_id_list(server_ids) if server_ids
58
+ params['cloudId'] = parse_cloud_id_list(cloud_ids) if cloud_ids
59
+ @projects_interface.setopts(options)
60
+ if options[:dry_run]
61
+ print_dry_run @projects_interface.dry.list(params)
62
+ return
63
+ end
64
+ json_response = @projects_interface.list(params)
65
+ projects = json_response['projects']
66
+ render_response(json_response, options, 'projects') do
67
+ title = "Morpheus Projects"
68
+ subtitles = []
69
+ subtitles += parse_list_subtitles(options)
70
+ print_h1 title, subtitles
71
+ if projects.empty?
72
+ print yellow,"No projects found.",reset,"\n"
73
+ else
74
+ print cyan
75
+ columns = [
76
+ {"ID" => lambda {|it| it['id'] } },
77
+ {"NAME" => lambda {|it| it['name'] } },
78
+ {"DESCRIPTION" => lambda {|it| it['description'] } },
79
+ {"INSTANCES" => lambda {|it| it['instances'].size rescue '' } },
80
+ {"SERVERS" => lambda {|it| it['servers'].size rescue '' } },
81
+ {"CLOUDS" => lambda {|it| it['clouds'].size rescue '' } },
82
+ {"OWNER" => lambda {|it| it['owner'] ? it['owner']['username'] : '' } },
83
+ {"DATE CREATED" => lambda {|it| format_local_dt(it['dateCreated']) } },
84
+ {"LAST UPDATED" => lambda {|it| format_local_dt(it['lastUpdated']) } },
85
+ ]
86
+ print as_pretty_table(projects, columns, options)
87
+ print_results_pagination(json_response)
88
+ end
89
+ print reset,"\n"
90
+ end
91
+ if projects.empty?
92
+ return 1, "No projects found"
93
+ else
94
+ return 0, nil
95
+ end
96
+ end
97
+
98
+ def get(args)
99
+ options = {}
100
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
101
+ opts.banner = subcommand_usage("[workflow]")
102
+ opts.on('--no-content', "Do not display script content." ) do
103
+ options[:no_content] = true
104
+ end
105
+ build_common_options(opts, options, [:json, :yaml, :csv, :fields, :dry_run, :remote])
106
+ opts.footer = <<-EOT
107
+ Get details about a project.
108
+ [project] is required. This is the name or id of a project.
109
+ EOT
110
+ end
111
+ optparse.parse!(args)
112
+ verify_args!(args:args, optparse:optparse, min:1)
113
+ connect(options)
114
+ id_list = parse_id_list(args)
115
+ return run_command_for_each_arg(id_list) do |arg|
116
+ _get(arg, options)
117
+ end
118
+ end
119
+
120
+ def _get(id, options)
121
+ @projects_interface.setopts(options)
122
+ if options[:dry_run]
123
+ if id.to_s =~ /\A\d{1,}\Z/
124
+ print_dry_run @projects_interface.dry.get(id.to_i)
125
+ else
126
+ print_dry_run @projects_interface.dry.get({name: id})
127
+ end
128
+ return
129
+ end
130
+ project = find_project_by_name_or_id(id)
131
+ exit 1 if project.nil?
132
+ # refetch it by id
133
+ json_response = {'project' => project}
134
+ unless id.to_s =~ /\A\d{1,}\Z/
135
+ json_response = @projects_interface.get(project['id'])
136
+ end
137
+ project = json_response['project']
138
+ render_response(json_response, options, 'project') do
139
+ print_h1 "Project Details"
140
+ print cyan
141
+ description_cols = {
142
+ "ID" => 'id',
143
+ "Name" => 'name',
144
+ "Description" => 'description',
145
+ "Tags" => lambda {|it| it['tags'] ? it['tags'].collect {|m| "#{m['name']}: #{m['value']}" }.join(', ') : '' },
146
+ "Owner" => lambda {|it| it['owner'] ? it['owner']['username'] : '' },
147
+ "Date Created" => lambda {|it| format_local_dt(it['dateCreated']) },
148
+ "Last Updated" => lambda {|it| format_local_dt(it['lastUpdated']) }
149
+ }
150
+ print_description_list(description_cols, project)
151
+
152
+ project_instances = project["instances"]
153
+ if project_instances && project_instances.size > 0
154
+ print_h2 "Instances"
155
+ print cyan
156
+ print as_pretty_table(project_instances, [:id, :name], options)
157
+ end
158
+
159
+ project_hosts = project["servers"] || project["hosts"]
160
+ if project_hosts && project_hosts.size > 0
161
+ print_h2 "Hosts"
162
+ print cyan
163
+ print as_pretty_table(project_hosts, [:id, :name], options)
164
+ end
165
+
166
+ project_clouds = project["clouds"] || project["zones"]
167
+ if project_clouds && project_clouds.size > 0
168
+ print_h2 "Clouds"
169
+ print cyan
170
+ print as_pretty_table(project_clouds, [:id, :name], options)
171
+ end
172
+ print reset,"\n"
173
+ end
174
+ return 0, nil
175
+ end
176
+
177
+ def add(args)
178
+ exit_code, err = 0, nil
179
+ params, options, payload = {}, {}, {}
180
+ project_name, description, metadata, instance_ids, server_ids, cloud_ids = nil, nil, nil, nil, nil, nil
181
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
182
+ opts.banner = subcommand_usage("[name]")
183
+ opts.on('--name NAME', String, "Project Name" ) do |val|
184
+ project_name = val
185
+ end
186
+ opts.on('--description NAME', String, "Project Description" ) do |val|
187
+ description = val
188
+ end
189
+ opts.on('--tags TAGS', String, "Tags in the format 'name:value, name:value'") do |val|
190
+ metadata = val
191
+ end
192
+ opts.on('--instances [LIST]', Array, "Instances, comma separated list of instance names or IDs.") do |list|
193
+ instance_ids = list.collect {|it| it.to_s.strip.empty? ? nil : it.to_s.strip }.compact.uniq
194
+ end
195
+ opts.on('--servers [LIST]', Array, "Servers, comma separated list of server (host) names or IDs.") do |list|
196
+ server_ids = list.collect {|it| it.to_s.strip.empty? ? nil : it.to_s.strip }.compact.uniq
197
+ end
198
+ opts.on('--clouds [LIST]', Array, "Clouds, comma separated list of cloud names or IDs.") do |list|
199
+ cloud_ids = list.collect {|it| it.to_s.strip.empty? ? nil : it.to_s.strip }.compact.uniq
200
+ end
201
+ build_standard_add_options(opts, options)
202
+ opts.footer = <<-EOT
203
+ Create a project.
204
+ [name] is required. This is the name of the new project.
205
+ EOT
206
+ end
207
+ optparse.parse!(args)
208
+ verify_args!(args:args, optparse:optparse, max:1)
209
+ if args[0]
210
+ project_name = args[0]
211
+ end
212
+ connect(options)
213
+ exit_code, err = 0, nil
214
+ # construct payload
215
+ if options[:payload]
216
+ payload = options[:payload]
217
+ payload.deep_merge!({'project' => parse_passed_options(options)})
218
+ else
219
+ payload['project'] = {}
220
+ payload.deep_merge!({'project' => parse_passed_options(options)})
221
+ payload['project']['name'] = project_name ? project_name : Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'name', 'fieldLabel' => 'Name', 'type' => 'text', 'required' => true, 'description' => 'Project Name'}], options[:options], @api_client)['name']
222
+ payload['project']['description'] = description ? description : Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'description', 'fieldLabel' => 'Description', 'type' => 'text', 'required' => false, 'description' => 'Project Description'}], options[:options], @api_client)['description']
223
+ # metadata tags
224
+ if metadata
225
+ metadata = parse_metadata(metadata)
226
+ payload['project']['tags'] = metadata if metadata
227
+ else
228
+ metadata = prompt_metadata(options)
229
+ payload['project']['tags'] = metadata if metadata
230
+ end
231
+ if instance_ids != nil
232
+ payload['project']['instances'] = parse_instance_id_list(instance_ids).collect { |it| {'id': it.to_i} }
233
+ else
234
+ # could prompt
235
+ end
236
+ # --instances
237
+ if instance_ids
238
+ payload['project']['instances'] = parse_instance_id_list(instance_ids).collect { |it| {'id': it.to_i} }
239
+ end
240
+ # --servers
241
+ if server_ids
242
+ payload['project']['servers'] = parse_server_id_list(server_ids).collect { |it| {'id': it.to_i} }
243
+ end
244
+ # --clouds
245
+ if cloud_ids
246
+ payload['project']['clouds'] = parse_cloud_id_list(cloud_ids).collect { |it| {'id': it.to_i} }
247
+ end
248
+ end
249
+ @projects_interface.setopts(options)
250
+ if options[:dry_run]
251
+ print_dry_run @projects_interface.dry.create(payload)
252
+ return
253
+ end
254
+ json_response = @projects_interface.create(payload)
255
+ project = json_response['project']
256
+ render_response(json_response, options, 'project') do
257
+ print_green_success "Project #{project['name']} created"
258
+ exit_code, err = get([project['id']] + (options[:remote] ? ["-r",options[:remote]] : []))
259
+ end
260
+ return exit_code, err
261
+ end
262
+
263
+ def update(args)
264
+ exit_code, err = 0, nil
265
+ params, options, payload = {}, {}, {}
266
+ project_name, description, metadata, add_metadata, remove_metadata = nil, nil, nil, nil, nil
267
+ instance_ids, server_ids, cloud_ids = nil, nil, nil
268
+ add_instance_ids, remove_instance_ids = nil, nil
269
+ add_server_ids, remove_server_ids = nil, nil
270
+ add_cloud_ids, remove_cloud_ids = nil, nil
271
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
272
+ opts.banner = subcommand_usage("[project] [options]")
273
+ opts.on('--name NAME', String, "Project Name" ) do |val|
274
+ project_name = val
275
+ end
276
+ opts.on('--description NAME', String, "Project Description" ) do |val|
277
+ description = val
278
+ end
279
+ opts.on('--tags [TAGS]', String, "Project Tags in the format 'name:value, name:value'. This replaces all project tags.") do |val|
280
+ metadata = val ? val : []
281
+ end
282
+ opts.on('--add-tags TAGS', String, "Add Project Tags in the format 'name:value, name:value'. This will add/update project tags.") do |val|
283
+ add_metadata = val
284
+ end
285
+ opts.on('--remove-tags TAGS', String, "Remove Project Tags in the format 'name, name:value'. This removes project tags, the :value component is optional and must match if passed.") do |val|
286
+ remove_metadata = val
287
+ end
288
+ opts.on('--instances [LIST]', Array, "Instances, comma separated list of instance names or IDs.") do |list|
289
+ instance_ids = list ? list.collect {|it| it.to_s.strip.empty? ? nil : it.to_s.strip }.compact.uniq : []
290
+ end
291
+ opts.on('--add-instances LIST', Array, "Add Instances, comma separated list of instance names or IDs to add.") do |list|
292
+ add_instance_ids = list.collect {|it| it.to_s.strip.empty? ? nil : it.to_s.strip }.compact.uniq
293
+ end
294
+ opts.on('--remove-instances LIST', Array, "Remove Instances, comma separated list of instance names or IDs to remove.") do |list|
295
+ remove_instance_ids = list.collect {|it| it.to_s.strip.empty? ? nil : it.to_s.strip }.compact.uniq
296
+ end
297
+ opts.on('--servers [LIST]', Array, "Servers, comma separated list of server (host) names or IDs.") do |list|
298
+ server_ids = list ? list.collect {|it| it.to_s.strip.empty? ? nil : it.to_s.strip }.compact.uniq : []
299
+ end
300
+ opts.on('--add-servers LIST', Array, "Add Servers, comma separated list of server names or IDs to add.") do |list|
301
+ add_server_ids = list.collect {|it| it.to_s.strip.empty? ? nil : it.to_s.strip }.compact.uniq
302
+ end
303
+ opts.on('--remove-servers LIST', Array, "Remove Servers, comma separated list of server names or IDs to remove.") do |list|
304
+ remove_server_ids = list.collect {|it| it.to_s.strip.empty? ? nil : it.to_s.strip }.compact.uniq
305
+ end
306
+ opts.on('--clouds [LIST]', Array, "Clouds, comma separated list of cloud names or IDs.") do |list|
307
+ cloud_ids = list ? list.collect {|it| it.to_s.strip.empty? ? nil : it.to_s.strip }.compact.uniq : []
308
+ end
309
+ opts.on('--add-clouds LIST', Array, "Add Clouds, comma separated list of cloud names or IDs to add.") do |list|
310
+ add_cloud_ids = list.collect {|it| it.to_s.strip.empty? ? nil : it.to_s.strip }.compact.uniq
311
+ end
312
+ opts.on('--remove-clouds LIST', Array, "Remove Clouds, comma separated list of cloud names or IDs to remove.") do |list|
313
+ remove_cloud_ids = list.collect {|it| it.to_s.strip.empty? ? nil : it.to_s.strip }.compact.uniq
314
+ end
315
+ build_standard_update_options(opts, options)
316
+ opts.footer = <<-EOT
317
+ Update a project.
318
+ [project] is required. This is the name or id of a project.
319
+ EOT
320
+ end
321
+ optparse.parse!(args)
322
+ verify_args!(args:args, optparse:optparse, count:1)
323
+ connect(options)
324
+ exit_code, err = 0, nil
325
+ project = find_project_by_name_or_id(args[0])
326
+ return 1, "project not found by '#{args[0]}'" if project.nil?
327
+ # construct payload
328
+ if options[:payload]
329
+ payload = options[:payload]
330
+ payload.deep_merge!({'project' => parse_passed_options(options)})
331
+ else
332
+ payload['project'] = {}
333
+ payload.deep_merge!({'project' => parse_passed_options(options)})
334
+ payload['project']['name'] = project_name if project_name
335
+ payload['project']['description'] = description if description
336
+ # metadata tags
337
+ if metadata
338
+ payload['project']['tags'] = parse_metadata(metadata)
339
+ end
340
+ if add_metadata
341
+ payload['project']['addTags'] = parse_metadata(add_metadata)
342
+ end
343
+ if remove_metadata
344
+ payload['project']['removeTags'] = parse_metadata(remove_metadata)
345
+ end
346
+ # --instances
347
+ if instance_ids
348
+ payload['project']['instances'] = parse_instance_id_list(instance_ids).collect { |it| {'id': it.to_i} }
349
+ end
350
+ if add_instance_ids
351
+ payload['project']['addInstances'] = parse_instance_id_list(add_instance_ids).collect { |it| {'id': it.to_i} }
352
+ end
353
+ if remove_instance_ids
354
+ payload['project']['removeInstances'] = parse_instance_id_list(remove_instance_ids).collect { |it| {'id': it.to_i} }
355
+ end
356
+ # --servers
357
+ if server_ids
358
+ payload['project']['servers'] = parse_server_id_list(server_ids).collect { |it| {'id': it.to_i} }
359
+ end
360
+ if add_server_ids
361
+ payload['project']['addServers'] = parse_server_id_list(add_server_ids).collect { |it| {'id': it.to_i} }
362
+ end
363
+ if remove_server_ids
364
+ payload['project']['removeServers'] = parse_server_id_list(remove_server_ids).collect { |it| {'id': it.to_i} }
365
+ end
366
+ # --clouds
367
+ if cloud_ids
368
+ cloud_ids = parse_cloud_id_list(cloud_ids)
369
+ return 1, "clouds not found" if cloud_ids.nil?
370
+ payload['project']['clouds'] = cloud_ids.collect { |it| {'id': it.to_i} }
371
+ end
372
+ if add_cloud_ids
373
+ add_cloud_ids = parse_cloud_id_list(add_cloud_ids)
374
+ return 1, "clouds not found" if add_cloud_ids.nil?
375
+ payload['project']['addClouds'] = add_cloud_ids.collect { |it| {'id': it.to_i} }
376
+ end
377
+ if remove_cloud_ids
378
+ remove_cloud_ids = parse_cloud_id_list(remove_cloud_ids)
379
+ return 1, "clouds not found" if remove_cloud_ids.nil?
380
+ payload['project']['removeClouds'] = remove_cloud_ids.collect { |it| {'id': it.to_i} }
381
+ end
382
+ end
383
+ @projects_interface.setopts(options)
384
+ if options[:dry_run]
385
+ print_dry_run @projects_interface.dry.update(project['id'], payload)
386
+ return
387
+ end
388
+ json_response = @projects_interface.update(project['id'], payload)
389
+ project = json_response['project']
390
+ render_response(json_response, options, 'project') do
391
+ print_green_success "Project #{project['name']} updated"
392
+ exit_code, err = get([project['id']] + (options[:remote] ? ["-r",options[:remote]] : []))
393
+ end
394
+ return exit_code, err
395
+ end
396
+
397
+ def add_tags(args)
398
+ params = {}
399
+ options = {}
400
+ payload = {}
401
+ metadata = nil
402
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
403
+ opts.banner = subcommand_usage("[project] [tag=value] [tag=value]")
404
+ opts.on('--tags TAGS', String, "Project Tags in the format 'name:value, name:value'. This will add/update project tags.") do |val|
405
+ metadata = val
406
+ end
407
+ build_standard_update_options(opts, options)
408
+ opts.footer = <<-EOT
409
+ Add metadata tags to a project.
410
+ [project] is required. This is the name or id of a project.
411
+ [tag=value] is required. This is a metadata tag in the format name=value, 1-N may be passed.
412
+ EOT
413
+ end
414
+ optparse.parse!(args)
415
+ verify_args!(args:args, optparse:optparse, min:1) # maybe min:2 instead ?
416
+ connect(options)
417
+ exit_code, err = 0, nil
418
+ if args.count > 1
419
+ metadata = args[1..-1].join(" ")
420
+ end
421
+ project = find_project_by_name_or_id(args[0])
422
+ return 1, "project not found by '#{args[0]}'" if project.nil?
423
+ # construct payload
424
+
425
+ if options[:payload]
426
+ payload = options[:payload]
427
+ payload.deep_merge!({'project' => parse_passed_options(options)})
428
+ else
429
+ payload['project'] = {}
430
+ payload.deep_merge!({'project' => parse_passed_options(options)})
431
+ if options[:metadata]
432
+ payload['project']['addTags'] = parse_metadata(metadata)
433
+ end
434
+ end
435
+ @projects_interface.setopts(options)
436
+ if options[:dry_run]
437
+ print_dry_run @projects_interface.dry.update(project['id'], payload)
438
+ return
439
+ end
440
+ json_response = @projects_interface.update(project['id'], payload)
441
+ project = json_response['project']
442
+ render_response(json_response, options, 'project') do
443
+ print_green_success "Project #{project['name']} updated"
444
+ exit_code, err = get([project['id']] + (options[:remote] ? ["-r",options[:remote]] : []))
445
+ end
446
+ return exit_code, err
447
+ end
448
+
449
+ def remove_tags(args)
450
+ params = {}
451
+ options = {}
452
+ payload = {}
453
+ metadata = nil
454
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
455
+ opts.banner = subcommand_usage("[project] [tags]")
456
+ opts.on('--tags TAGS', String, "Project Tags in the format 'name=value, name=value'. This will add/update project tags.") do |val|
457
+ metadata = val
458
+ end
459
+ build_standard_update_options(opts, options)
460
+ opts.footer = <<-EOT
461
+ Remove metadata tags from a project.
462
+ [project] is required. This is the name or id of a project.
463
+ [tags] is required. This is a metadata tag in the format tag=value, tag2, tag3, 1-N may be passed, value is optional and must match if passed.
464
+ EOT
465
+ end
466
+ optparse.parse!(args)
467
+ verify_args!(args:args, optparse:optparse, min:1) # maybe min:2 instead ?
468
+ connect(options)
469
+ exit_code, err = 0, nil
470
+ if args.count > 1
471
+ metadata = args[1..-1].join(" ")
472
+ end
473
+ project = find_project_by_name_or_id(args[0])
474
+ return 1, "project not found by '#{args[0]}'" if project.nil?
475
+ # construct payload
476
+
477
+ if options[:payload]
478
+ payload = options[:payload]
479
+ payload.deep_merge!({'project' => parse_passed_options(options)})
480
+ else
481
+ payload['project'] = {}
482
+ payload.deep_merge!({'project' => parse_passed_options(options)})
483
+ if metadata
484
+ payload['project']['removeTags'] = parse_metadata(metadata)
485
+ end
486
+ end
487
+ @projects_interface.setopts(options)
488
+ if options[:dry_run]
489
+ print_dry_run @projects_interface.dry.update(project['id'], payload)
490
+ return
491
+ end
492
+ json_response = @projects_interface.update(project['id'], payload)
493
+ project = json_response['project']
494
+ render_response(json_response, options, 'project') do
495
+ print_green_success "Project #{project['name']} updated"
496
+ exit_code, err = get([project['id']] + (options[:remote] ? ["-r",options[:remote]] : []))
497
+ end
498
+ return exit_code, err
499
+ end
500
+
501
+ def remove(args)
502
+ params = {}
503
+ options = {}
504
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
505
+ opts.banner = subcommand_usage("[project]")
506
+ build_common_options(opts, options, [:auto_confirm, :json, :dry_run, :quiet, :remote])
507
+ # opts.on( '-f', '--force', "Force Delete" ) do
508
+ # params[:force] = true
509
+ # end
510
+ opts.footer = <<-EOT
511
+ Delete a project.
512
+ [project] is required. This is the name or id of a project.
513
+ EOT
514
+ end
515
+ optparse.parse!(args)
516
+ verify_args!(args:args, optparse:optparse, count:1)
517
+ connect(options)
518
+ exit_code, err = 0, nil
519
+ project = find_project_by_name_or_id(args[0])
520
+ return 1, "project not found by '#{args[0]}'" if project.nil?
521
+ unless options[:yes] || Morpheus::Cli::OptionTypes.confirm("Are you sure you want to delete the project #{project['name']}?")
522
+ return 9, "aborted command"
523
+ end
524
+ @projects_interface.setopts(options)
525
+ if options[:dry_run]
526
+ print_dry_run @projects_interface.dry.destroy(project['id'], params)
527
+ return
528
+ end
529
+ json_response = @projects_interface.destroy(project['id'], params)
530
+ render_response(json_response, options) do
531
+ print_green_success "Project #{project['name']} removed"
532
+ end
533
+ return exit_code, err
534
+ end
535
+
536
+ private
537
+
538
+ def find_project_by_name_or_id(val)
539
+ if val.to_s =~ /\A\d{1,}\Z/
540
+ return find_project_by_id(val)
541
+ else
542
+ return find_project_by_name(val)
543
+ end
544
+ end
545
+
546
+ def find_project_by_id(id)
547
+ begin
548
+ json_response = @projects_interface.get(id.to_i)
549
+ return json_response['project']
550
+ rescue RestClient::Exception => e
551
+ if e.response && e.response.code == 404
552
+ print_red_alert "Project not found by id #{id}"
553
+ return nil
554
+ else
555
+ raise e
556
+ end
557
+ end
558
+ end
559
+
560
+ def find_project_by_name(name)
561
+ projects = @projects_interface.list({name: name.to_s})['projects']
562
+ if projects.empty?
563
+ print_red_alert "Project not found by name #{name}"
564
+ return nil
565
+ elsif projects.size > 1
566
+ print_red_alert "#{projects.size} projects by name #{name}"
567
+ print_error "\n"
568
+ puts_error as_pretty_table(projects, [:id, :name], {color:red})
569
+ print_red_alert "Try passing ID instead"
570
+ return nil
571
+ else
572
+ return projects[0]
573
+ end
574
+ end
575
+
576
+ def find_project_type_by_name(val)
577
+ raise "find_project_type_by_name passed a bad name: #{val.inspect}" if val.to_s == ''
578
+ @all_project_types ||= @projects_interface.list_types({max:1000})['projectTypes']
579
+
580
+ if @all_project_types.nil? && !@all_project_types.empty?
581
+ print_red_alert "No project types found"
582
+ return nil
583
+ end
584
+ matching_project_types = @all_project_types.select { |it| val && (it['name'] == val || it['code'] == val || it['id'].to_s == val.to_s) }
585
+ if matching_project_types.size == 1
586
+ return matching_project_types[0]
587
+ elsif matching_project_types.size == 0
588
+ print_red_alert "Project Type not found by '#{val}'"
589
+ else
590
+ print_red_alert "#{matching_project_types.size} project types found by name #{name}"
591
+ rows = matching_project_types.collect do |it|
592
+ {id: it['id'], name: it['name'], code: it['code']}
593
+ end
594
+ print "\n"
595
+ puts as_pretty_table(rows, [:name, :code], {color:red})
596
+ return nil
597
+ end
598
+ end
599
+
600
+ def update_project_option_types(project_type)
601
+ [
602
+ {'fieldName' => 'name', 'fieldLabel' => 'Name', 'type' => 'text', 'required' => true, 'displayOrder' => 0}
603
+ ] + project_type['optionTypes']
604
+ end
605
+
606
+
607
+ end