morpheus-cli 5.3.1 → 5.3.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (173) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +1 -1
  3. data/lib/morpheus/api/account_groups_interface.rb +0 -6
  4. data/lib/morpheus/api/accounts_interface.rb +4 -36
  5. data/lib/morpheus/api/api_client.rb +120 -106
  6. data/lib/morpheus/api/appliance_settings_interface.rb +6 -9
  7. data/lib/morpheus/api/approvals_interface.rb +5 -8
  8. data/lib/morpheus/api/apps_interface.rb +0 -7
  9. data/lib/morpheus/api/archive_buckets_interface.rb +9 -16
  10. data/lib/morpheus/api/archive_files_interface.rb +0 -6
  11. data/lib/morpheus/api/auth_interface.rb +4 -4
  12. data/lib/morpheus/api/backup_settings_interface.rb +5 -8
  13. data/lib/morpheus/api/blueprints_interface.rb +1 -7
  14. data/lib/morpheus/api/budgets_interface.rb +0 -6
  15. data/lib/morpheus/api/cloud_datastores_interface.rb +0 -6
  16. data/lib/morpheus/api/cloud_folders_interface.rb +1 -7
  17. data/lib/morpheus/api/cloud_policies_interface.rb +0 -6
  18. data/lib/morpheus/api/cloud_resource_pools_interface.rb +0 -6
  19. data/lib/morpheus/api/clouds_interface.rb +0 -6
  20. data/lib/morpheus/api/clusters_interface.rb +39 -42
  21. data/lib/morpheus/api/containers_interface.rb +0 -6
  22. data/lib/morpheus/api/custom_instance_types_interface.rb +0 -6
  23. data/lib/morpheus/api/cypher_interface.rb +0 -6
  24. data/lib/morpheus/api/datastores_interface.rb +4 -7
  25. data/lib/morpheus/api/deploy_interface.rb +1 -6
  26. data/lib/morpheus/api/environments_interface.rb +0 -6
  27. data/lib/morpheus/api/execute_schedules_interface.rb +0 -6
  28. data/lib/morpheus/api/execution_request_interface.rb +0 -6
  29. data/lib/morpheus/api/file_copy_request_interface.rb +2 -9
  30. data/lib/morpheus/api/group_policies_interface.rb +0 -6
  31. data/lib/morpheus/api/groups_interface.rb +0 -7
  32. data/lib/morpheus/api/guidance_interface.rb +9 -12
  33. data/lib/morpheus/api/health_interface.rb +0 -6
  34. data/lib/morpheus/api/image_builder_boot_scripts_interface.rb +0 -6
  35. data/lib/morpheus/api/image_builder_image_builds_interface.rb +0 -6
  36. data/lib/morpheus/api/image_builder_interface.rb +3 -9
  37. data/lib/morpheus/api/image_builder_preseed_scripts_interface.rb +0 -6
  38. data/lib/morpheus/api/instance_types_interface.rb +0 -7
  39. data/lib/morpheus/api/instances_interface.rb +8 -19
  40. data/lib/morpheus/api/integrations_interface.rb +30 -0
  41. data/lib/morpheus/api/invoice_line_items_interface.rb +4 -9
  42. data/lib/morpheus/api/jobs_interface.rb +11 -14
  43. data/lib/morpheus/api/key_pairs_interface.rb +0 -6
  44. data/lib/morpheus/api/library_cluster_layouts_interface.rb +0 -6
  45. data/lib/morpheus/api/library_container_scripts_interface.rb +0 -6
  46. data/lib/morpheus/api/library_container_templates_interface.rb +0 -6
  47. data/lib/morpheus/api/library_container_types_interface.rb +0 -6
  48. data/lib/morpheus/api/library_container_upgrades_interface.rb +0 -6
  49. data/lib/morpheus/api/library_instance_types_interface.rb +0 -6
  50. data/lib/morpheus/api/library_layouts_interface.rb +0 -6
  51. data/lib/morpheus/api/library_spec_template_types_interface.rb +0 -6
  52. data/lib/morpheus/api/library_spec_templates_interface.rb +0 -6
  53. data/lib/morpheus/api/license_interface.rb +0 -6
  54. data/lib/morpheus/api/load_balancer_pools_interface.rb +9 -0
  55. data/lib/morpheus/api/load_balancer_types_interface.rb +9 -0
  56. data/lib/morpheus/api/load_balancer_virtual_servers_interface.rb +9 -0
  57. data/lib/morpheus/api/load_balancers_interface.rb +4 -59
  58. data/lib/morpheus/api/log_settings_interface.rb +9 -12
  59. data/lib/morpheus/api/logs_interface.rb +0 -6
  60. data/lib/morpheus/api/monitoring_alerts_interface.rb +0 -6
  61. data/lib/morpheus/api/monitoring_apps_interface.rb +0 -6
  62. data/lib/morpheus/api/monitoring_checks_interface.rb +0 -6
  63. data/lib/morpheus/api/monitoring_contacts_interface.rb +0 -6
  64. data/lib/morpheus/api/monitoring_groups_interface.rb +0 -6
  65. data/lib/morpheus/api/monitoring_incidents_interface.rb +0 -6
  66. data/lib/morpheus/api/monitoring_interface.rb +6 -12
  67. data/lib/morpheus/api/network_domain_records_interface.rb +0 -6
  68. data/lib/morpheus/api/network_domains_interface.rb +0 -6
  69. data/lib/morpheus/api/network_groups_interface.rb +0 -6
  70. data/lib/morpheus/api/network_pool_ips_interface.rb +0 -6
  71. data/lib/morpheus/api/network_pool_servers_interface.rb +0 -6
  72. data/lib/morpheus/api/network_pools_interface.rb +0 -6
  73. data/lib/morpheus/api/network_proxies_interface.rb +0 -6
  74. data/lib/morpheus/api/network_routers_interface.rb +56 -6
  75. data/lib/morpheus/api/network_security_servers_interface.rb +6 -9
  76. data/lib/morpheus/api/network_services_interface.rb +0 -14
  77. data/lib/morpheus/api/network_subnets_interface.rb +0 -6
  78. data/lib/morpheus/api/network_types_interface.rb +1 -7
  79. data/lib/morpheus/api/networks_interface.rb +0 -6
  80. data/lib/morpheus/api/option_type_lists_interface.rb +18 -12
  81. data/lib/morpheus/api/option_types_interface.rb +0 -6
  82. data/lib/morpheus/api/options_interface.rb +0 -6
  83. data/lib/morpheus/api/packages_interface.rb +0 -6
  84. data/lib/morpheus/api/policies_interface.rb +1 -8
  85. data/lib/morpheus/api/power_schedules_interface.rb +0 -6
  86. data/lib/morpheus/api/price_sets_interface.rb +8 -11
  87. data/lib/morpheus/api/prices_interface.rb +12 -15
  88. data/lib/morpheus/api/processes_interface.rb +0 -6
  89. data/lib/morpheus/api/provision_types_interface.rb +0 -6
  90. data/lib/morpheus/api/provisioning_license_types_interface.rb +0 -6
  91. data/lib/morpheus/api/provisioning_licenses_interface.rb +0 -6
  92. data/lib/morpheus/api/provisioning_settings_interface.rb +6 -9
  93. data/lib/morpheus/api/reports_interface.rb +0 -6
  94. data/lib/morpheus/api/roles_interface.rb +0 -6
  95. data/lib/morpheus/api/secondary_read_interface.rb +25 -0
  96. data/lib/morpheus/api/secondary_rest_interface.rb +42 -0
  97. data/lib/morpheus/api/security_group_rules_interface.rb +0 -7
  98. data/lib/morpheus/api/security_groups_interface.rb +0 -6
  99. data/lib/morpheus/api/server_types_interface.rb +0 -6
  100. data/lib/morpheus/api/servers_interface.rb +0 -6
  101. data/lib/morpheus/api/service_plans_interface.rb +11 -14
  102. data/lib/morpheus/api/storage_providers_interface.rb +9 -16
  103. data/lib/morpheus/api/subnet_types_interface.rb +1 -7
  104. data/lib/morpheus/api/subnets_interface.rb +0 -6
  105. data/lib/morpheus/api/task_sets_interface.rb +0 -6
  106. data/lib/morpheus/api/tasks_interface.rb +0 -6
  107. data/lib/morpheus/api/user_groups_interface.rb +0 -6
  108. data/lib/morpheus/api/user_sources_interface.rb +0 -6
  109. data/lib/morpheus/api/users_interface.rb +0 -6
  110. data/lib/morpheus/api/virtual_images_interface.rb +0 -6
  111. data/lib/morpheus/api/whitelabel_settings_interface.rb +8 -11
  112. data/lib/morpheus/api/wiki_interface.rb +0 -6
  113. data/lib/morpheus/cli/access_token_command.rb +1 -1
  114. data/lib/morpheus/cli/account_groups_command.rb +4 -4
  115. data/lib/morpheus/cli/apps.rb +9 -9
  116. data/lib/morpheus/cli/archives_command.rb +5 -5
  117. data/lib/morpheus/cli/blueprints_command.rb +5 -5
  118. data/lib/morpheus/cli/boot_scripts_command.rb +1 -1
  119. data/lib/morpheus/cli/change_password_command.rb +4 -4
  120. data/lib/morpheus/cli/cli_command.rb +11 -10
  121. data/lib/morpheus/cli/clouds.rb +2 -2
  122. data/lib/morpheus/cli/clusters.rb +2 -2
  123. data/lib/morpheus/cli/credentials.rb +4 -11
  124. data/lib/morpheus/cli/environments_command.rb +1 -1
  125. data/lib/morpheus/cli/execute_schedules_command.rb +3 -3
  126. data/lib/morpheus/cli/hosts.rb +8 -8
  127. data/lib/morpheus/cli/image_builder_command.rb +6 -6
  128. data/lib/morpheus/cli/instance_types.rb +1 -1
  129. data/lib/morpheus/cli/instances.rb +93 -64
  130. data/lib/morpheus/cli/integrations_command.rb +567 -1
  131. data/lib/morpheus/cli/invoices_command.rb +75 -67
  132. data/lib/morpheus/cli/key_pairs.rb +2 -2
  133. data/lib/morpheus/cli/library_cluster_layouts_command.rb +2 -3
  134. data/lib/morpheus/cli/library_container_scripts_command.rb +4 -5
  135. data/lib/morpheus/cli/library_container_templates_command.rb +5 -1
  136. data/lib/morpheus/cli/library_container_types_command.rb +8 -9
  137. data/lib/morpheus/cli/library_instance_types_command.rb +6 -7
  138. data/lib/morpheus/cli/library_layouts_command.rb +9 -5
  139. data/lib/morpheus/cli/library_option_lists_command.rb +72 -20
  140. data/lib/morpheus/cli/library_option_types_command.rb +8 -4
  141. data/lib/morpheus/cli/library_spec_templates_command.rb +3 -4
  142. data/lib/morpheus/cli/library_upgrades_command.rb +6 -6
  143. data/lib/morpheus/cli/license.rb +2 -2
  144. data/lib/morpheus/cli/load_balancer_types.rb +37 -0
  145. data/lib/morpheus/cli/load_balancers.rb +149 -314
  146. data/lib/morpheus/cli/log_settings_command.rb +7 -3
  147. data/lib/morpheus/cli/login.rb +3 -1
  148. data/lib/morpheus/cli/mixins/load_balancers_helper.rb +156 -0
  149. data/lib/morpheus/cli/mixins/print_helper.rb +11 -0
  150. data/lib/morpheus/cli/mixins/provisioning_helper.rb +2 -2
  151. data/lib/morpheus/cli/mixins/rest_command.rb +657 -0
  152. data/lib/morpheus/cli/network_routers_command.rb +1183 -185
  153. data/lib/morpheus/cli/networks_command.rb +194 -101
  154. data/lib/morpheus/cli/option_types.rb +34 -40
  155. data/lib/morpheus/cli/policies_command.rb +0 -1
  156. data/lib/morpheus/cli/power_schedules_command.rb +3 -3
  157. data/lib/morpheus/cli/preseed_scripts_command.rb +1 -1
  158. data/lib/morpheus/cli/remote.rb +1 -1
  159. data/lib/morpheus/cli/roles.rb +9 -9
  160. data/lib/morpheus/cli/security_group_rules.rb +1 -1
  161. data/lib/morpheus/cli/setup.rb +0 -1
  162. data/lib/morpheus/cli/tenants_command.rb +21 -23
  163. data/lib/morpheus/cli/user_groups_command.rb +3 -3
  164. data/lib/morpheus/cli/user_sources_command.rb +3 -3
  165. data/lib/morpheus/cli/users.rb +3 -3
  166. data/lib/morpheus/cli/version.rb +1 -1
  167. data/lib/morpheus/cli/virtual_images.rb +1 -1
  168. data/lib/morpheus/cli/whoami.rb +0 -15
  169. data/lib/morpheus/cli/wiki_command.rb +1 -1
  170. data/lib/morpheus/cli.rb +1 -0
  171. data/lib/morpheus/ext/string.rb +41 -0
  172. data/lib/morpheus/formatters.rb +4 -0
  173. metadata +11 -2
@@ -32,14 +32,14 @@ class Morpheus::Cli::Hosts
32
32
  @api_client = establish_remote_appliance_connection(opts)
33
33
  @accounts_interface = @api_client.accounts
34
34
  @users_interface = @api_client.users
35
- @clouds_interface = Morpheus::APIClient.new(@access_token,nil,nil, @appliance_url).clouds
36
- @options_interface = Morpheus::APIClient.new(@access_token,nil,nil, @appliance_url).options
37
- @tasks_interface = Morpheus::APIClient.new(@access_token,nil,nil, @appliance_url).tasks
38
- @task_sets_interface = Morpheus::APIClient.new(@access_token,nil,nil, @appliance_url).task_sets
39
- @servers_interface = Morpheus::APIClient.new(@access_token,nil,nil, @appliance_url).servers
40
- @server_types_interface = Morpheus::APIClient.new(@access_token,nil,nil, @appliance_url).server_types
41
- @logs_interface = Morpheus::APIClient.new(@access_token,nil,nil, @appliance_url).logs
42
- @accounts_interface = Morpheus::APIClient.new(@access_token,nil,nil, @appliance_url).accounts
35
+ @clouds_interface = @api_client.clouds
36
+ @options_interface = @api_client.options
37
+ @tasks_interface = @api_client.tasks
38
+ @task_sets_interface = @api_client.task_sets
39
+ @servers_interface = @api_client.servers
40
+ @server_types_interface = @api_client.server_types
41
+ @logs_interface = @api_client.logs
42
+ @accounts_interface = @api_client.accounts
43
43
  @active_group_id = Morpheus::Cli::Groups.active_groups[@appliance_name]
44
44
  @execution_request_interface = @api_client.execution_request
45
45
  @clusters_interface = @api_client.clusters
@@ -30,15 +30,15 @@ class Morpheus::Cli::ImageBuilderCommand
30
30
 
31
31
  def connect(opts)
32
32
  @api_client = establish_remote_appliance_connection(opts)
33
- @image_builder_interface = Morpheus::APIClient.new(@access_token,nil,nil, @appliance_url).image_builder
33
+ @image_builder_interface = @api_client.image_builder
34
34
  @image_builds_interface = @image_builder_interface.image_builds
35
35
  @boot_scripts_interface = @image_builder_interface.boot_scripts
36
36
  @preseed_scripts_interface = @image_builder_interface.preseed_scripts
37
- @groups_interface = Morpheus::APIClient.new(@access_token,nil,nil, @appliance_url).groups
38
- @clouds_interface = Morpheus::APIClient.new(@access_token,nil,nil, @appliance_url).clouds
39
- @instances_interface = Morpheus::APIClient.new(@access_token,nil,nil, @appliance_url).instances
40
- @instance_types_interface = Morpheus::APIClient.new(@access_token,nil,nil, @appliance_url).instance_types
41
- @options_interface = Morpheus::APIClient.new(@access_token,nil,nil, @appliance_url).options
37
+ @groups_interface = @api_client.groups
38
+ @clouds_interface = @api_client.clouds
39
+ @instances_interface = @api_client.instances
40
+ @instance_types_interface = @api_client.instance_types
41
+ @options_interface = @api_client.options
42
42
  @active_group_id = Morpheus::Cli::Groups.active_groups[@appliance_name]
43
43
  end
44
44
 
@@ -17,7 +17,7 @@ class Morpheus::Cli::InstanceTypes
17
17
 
18
18
  def connect(opts)
19
19
  @api_client = establish_remote_appliance_connection(opts)
20
- @instance_types_interface = Morpheus::APIClient.new(@access_token,nil,nil, @appliance_url).instance_types
20
+ @instance_types_interface = @api_client.instance_types
21
21
  end
22
22
 
23
23
  def handle(args)
@@ -130,6 +130,7 @@ class Morpheus::Cli::Instances
130
130
  end
131
131
  opts.on('-a', '--details', "Display all details: plan, stats, etc" ) do
132
132
  options[:details] = true
133
+ params['details'] = true # get more data from server this way
133
134
  end
134
135
  build_standard_list_options(opts, options)
135
136
  opts.footer = "List instances."
@@ -1201,7 +1202,15 @@ class Morpheus::Cli::Instances
1201
1202
  options[:details] = true
1202
1203
  options[:include_containers] = true
1203
1204
  options[:include_scaling] = true
1205
+ options[:include_costs]
1204
1206
  end
1207
+ opts.on(nil, '--details', "Alias for --all" ) do
1208
+ options[:details] = true
1209
+ options[:include_containers] = true
1210
+ options[:include_scaling] = true
1211
+ options[:include_costs]
1212
+ end
1213
+ opts.add_hidden_option('--details')
1205
1214
  opts.on( nil, '--containers', "Display Instance Containers" ) do
1206
1215
  options[:include_containers] = true
1207
1216
  end
@@ -1216,9 +1225,6 @@ class Morpheus::Cli::Instances
1216
1225
  opts.on( nil, '--scaling', "Display Instance Scaling Settings" ) do
1217
1226
  options[:include_scaling] = true
1218
1227
  end
1219
- opts.on( nil, '--costs', "Display Cost and Price" ) do
1220
- options[:include_costs] = true
1221
- end
1222
1228
  opts.on('--refresh [SECONDS]', String, "Refresh until status is running,failed. Default interval is #{default_refresh_interval} seconds.") do |val|
1223
1229
  options[:refresh_until_status] ||= "running,failed"
1224
1230
  if !val.to_s.empty?
@@ -1250,22 +1256,29 @@ class Morpheus::Cli::Instances
1250
1256
  end
1251
1257
  end
1252
1258
 
1253
- def _get(arg, options={})
1254
-
1259
+ def _get(id, options={})
1260
+ params = {}
1261
+ params.merge!(parse_query_options(options))
1262
+ # Use details=true to get more details from the appliance
1263
+ # if options[:details] || options[:include_containers] || options[:include_scaling]
1264
+ if options[:details] || options[:include_containers] || options[:include_scaling]
1265
+ params['details'] = true
1266
+ end
1267
+ instance = nil
1268
+ if id.to_s !~ /\A\d{1,}\Z/
1269
+ instance = find_instance_by_name_or_id(id)
1270
+ return 1, "Instance not found by name #{id}" if instance.nil?
1271
+ id = instance['id']
1272
+ end
1255
1273
  if options[:dry_run]
1256
- @instances_interface.setopts(options)
1257
- if arg.to_s =~ /\A\d{1,}\Z/
1258
- print_dry_run @instances_interface.dry.get(arg.to_i)
1259
- else
1260
- print_dry_run @instances_interface.dry.get({name:arg})
1261
- end
1262
- return
1274
+ print_dry_run @instances_interface.dry.get(id, params)
1275
+ return 0, nil
1263
1276
  end
1264
- instance = find_instance_by_name_or_id(arg =~ /\A\d{1,}\Z/ ? arg.to_i : arg)
1265
1277
  @instances_interface.setopts(options)
1266
- json_response = @instances_interface.get(instance['id'])
1278
+ json_response = @instances_interface.get(id, params)
1267
1279
  render_response(json_response, options, "instance") do
1268
1280
  instance = json_response['instance']
1281
+ pricing = instance['instancePrice']
1269
1282
  stats = instance['stats'] || json_response['stats'] || {}
1270
1283
  # load_balancers = json_response['loadBalancers'] || {}
1271
1284
  # metadata tags used to be returned as metadata and are now returned as tags
@@ -1283,7 +1296,12 @@ class Morpheus::Cli::Instances
1283
1296
  # containers are fetched via separate api call
1284
1297
  containers = nil
1285
1298
  if options[:include_containers]
1286
- containers = @instances_interface.containers(instance['id'])['containers']
1299
+ # todo: can use instance['containerDetails'] in api 5.2.7/5.3.2
1300
+ if instance['containerDetails']
1301
+ containers = instance['containerDetails']
1302
+ else
1303
+ containers = @instances_interface.containers(instance['id'])['containers']
1304
+ end
1287
1305
  end
1288
1306
 
1289
1307
  # threshold is fetched via separate api call too
@@ -1317,8 +1335,20 @@ class Morpheus::Cli::Instances
1317
1335
  "Layout" => lambda {|it| it['layout'] ? it['layout']['name'] : '' },
1318
1336
  "Version" => lambda {|it| it['instanceVersion'] },
1319
1337
  "Plan" => lambda {|it| it['plan'] ? it['plan']['name'] : '' },
1320
- # "Cost" => lambda {|it| it['hourlyCost'] ? format_money(it['hourlyCost'], (it['currency'] || 'USD'), {sigdig:15}).to_s + ' per hour' : '' },
1321
- # "Price" => lambda {|it| it['hourlyPrice'] ? format_money(it['hourlyPrice'], (it['currency'] || 'USD'), {sigdig:15}).to_s + ' per hour' : '' },
1338
+ "Price" => lambda {|it|
1339
+ if pricing
1340
+ pricing['price'] ? format_money(pricing['price'], (pricing['currency'] || 'USD')).to_s + ' per ' + pricing['unit'].to_s : ''
1341
+ elsif it['hourlyPrice']
1342
+ format_money(it['hourlyPrice'], (it['currency'] || 'USD')).to_s + ' per hour'
1343
+ end
1344
+ },
1345
+ "Cost" => lambda {|it|
1346
+ if pricing
1347
+ pricing['cost'] ? format_money(pricing['cost'], (pricing['currency'] || 'USD')).to_s + ' per ' + pricing['unit'].to_s : ''
1348
+ elsif it['hourlyCost']
1349
+ format_money(it['hourlyCost'], (it['currency'] || 'USD')).to_s + ' per hour'
1350
+ end
1351
+ },
1322
1352
  "Environment" => 'instanceContext',
1323
1353
  "Labels" => lambda {|it| labels ? labels.join(',') : '' },
1324
1354
  "Tags" => lambda {|it| tags ? tags.collect {|m| "#{m['name']}: #{m['value']}" }.join(', ') : '' },
@@ -1330,6 +1360,7 @@ class Morpheus::Cli::Instances
1330
1360
  end
1331
1361
  },
1332
1362
  #"Tenant" => lambda {|it| it['tenant'] ? it['tenant']['name'] : '' },
1363
+ "Apps" => lambda {|it| anded_list(it['apps'] ? it['apps'].collect {|app| app['name'] } : [])},
1333
1364
  "Date Created" => lambda {|it| format_local_dt(it['dateCreated']) },
1334
1365
  # "Last Updated" => lambda {|it| format_local_dt(it['lastUpdated']) },
1335
1366
  "Power Schedule" => lambda {|it| (it['powerSchedule'] && it['powerSchedule']['type']) ? it['powerSchedule']['type']['name'] : '' },
@@ -1343,12 +1374,17 @@ class Morpheus::Cli::Instances
1343
1374
  }
1344
1375
  description_cols.delete("Labels") if labels.nil? || labels.empty?
1345
1376
  description_cols.delete("Tags") if tags.nil? || tags.empty?
1377
+ description_cols.delete("Apps") if instance['apps'].nil? || instance['apps'].empty?
1346
1378
  description_cols.delete("Power Schedule") if instance['powerSchedule'].nil?
1347
1379
  description_cols.delete("Expire Date") if instance['expireDate'].nil?
1348
1380
  description_cols.delete("Shutdown Date") if instance['shutdownDate'].nil?
1349
1381
  description_cols["Removal Date"] = lambda {|it| format_local_dt(it['removalDate'])} if instance['status'] == 'pendingRemoval'
1350
1382
  description_cols.delete("Last Deployment") if instance['lastDeploy'].nil?
1351
1383
  description_cols.delete("Locked") if instance['locked'] != true
1384
+ price_value = (pricing ? pricing['price'] : instance['hourlyPrice']).to_i
1385
+ cost_value = (pricing ? pricing['cost'] : instance['hourlyCost']).to_i
1386
+ description_cols.delete("Price") if price_value == 0
1387
+ description_cols.delete("Cost") if cost_value == 0 || cost_value == price_value
1352
1388
  #description_cols.delete("Environment") if instance['instanceContext'].nil?
1353
1389
  print_description_list(description_cols, instance)
1354
1390
 
@@ -1380,15 +1416,6 @@ class Morpheus::Cli::Instances
1380
1416
  print_stats_usage(stats)
1381
1417
  end
1382
1418
 
1383
- if options[:include_costs]
1384
- print_h2 "Instance Cost"
1385
- cost_columns = {
1386
- "Cost" => lambda {|it| it['hourlyCost'] ? format_money(it['hourlyCost'], (it['currency'] || 'USD'), {sigdig:15}).to_s + ' per hour' : '' },
1387
- "Price" => lambda {|it| it['hourlyPrice'] ? format_money(it['hourlyPrice'], (it['currency'] || 'USD'), {sigdig:15}).to_s + ' per hour' : '' },
1388
- }
1389
- print_description_list(cost_columns, instance)
1390
- end
1391
-
1392
1419
  print reset, "\n"
1393
1420
 
1394
1421
  # if options[:include_lb]
@@ -1478,7 +1505,7 @@ class Morpheus::Cli::Instances
1478
1505
  print cyan, "Refreshing in #{options[:refresh_interval] > 1 ? options[:refresh_interval].to_i : options[:refresh_interval]} seconds"
1479
1506
  sleep_with_dots(options[:refresh_interval])
1480
1507
  print "\n"
1481
- _get(arg, options)
1508
+ _get(instance['id'], options)
1482
1509
  end
1483
1510
  end
1484
1511
  end
@@ -2538,46 +2565,50 @@ class Morpheus::Cli::Instances
2538
2565
  options = {}
2539
2566
  optparse = Morpheus::Cli::OptionParser.new do |opts|
2540
2567
  opts.banner = subcommand_usage("[instance]")
2541
- build_common_options(opts, options, [:options, :json, :dry_run, :remote])
2568
+ build_standard_update_options(opts, options)
2542
2569
  end
2543
2570
  optparse.parse!(args)
2544
- if args.count < 1
2545
- puts optparse
2546
- exit 1
2547
- end
2571
+ verify_args!(args:args, optparse:optparse, count:1)
2548
2572
  connect(options)
2549
- begin
2550
- instance = find_instance_by_name_or_id(args[0])
2551
-
2552
- group_id = instance['group']['id']
2553
- cloud_id = instance['cloud']['id']
2554
- layout_id = instance['layout']['id']
2555
-
2556
- plan_id = instance['plan']['id']
2573
+ instance = find_instance_by_name_or_id(args[0])
2574
+ return 1, "instance not found" if instance.nil?
2575
+
2576
+ payload = {}
2577
+ if options[:payload]
2578
+ payload = options[:payload]
2579
+ payload.deep_merge!(parse_passed_options(options))
2580
+ else
2557
2581
  payload = {
2558
- :instance => {:id => instance["id"]}
2582
+ "instance" => {:id => instance["id"]}
2559
2583
  }
2584
+ payload.deep_merge!(parse_passed_options(options))
2560
2585
 
2561
2586
  # avoid 500 error
2562
2587
  # payload[:servicePlanOptions] = {}
2563
2588
 
2564
2589
  puts "\nDue to limitations by most Guest Operating Systems, Disk sizes can only be expanded and not reduced.\nIf a smaller plan is selected, memory and CPU (if relevant) will be reduced but storage will not.\n\n"
2565
2590
 
2591
+ group_id = instance['group']['id']
2592
+ cloud_id = instance['cloud']['id']
2593
+ layout_id = instance['layout']['id']
2594
+ plan_id = instance['plan']['id']
2595
+ current_plan_name = instance['plan']['name']
2566
2596
  # prompt for service plan
2567
2597
  service_plans_json = @instances_interface.service_plans({zoneId: cloud_id, siteId: group_id, layoutId: layout_id})
2568
2598
  service_plans = service_plans_json["plans"]
2569
2599
  service_plans_dropdown = service_plans.collect {|sp| {'name' => sp["name"], 'value' => sp["id"]} } # already sorted
2570
2600
  service_plans_dropdown.each do |plan|
2571
- if plan['value'] && plan['value'].to_i == plan_id.to_i
2572
- plan['name'] = "#{plan['name']} (current)"
2573
- end
2601
+ # if plan['value'] && plan['value'].to_i == plan_id.to_i
2602
+ # plan['name'] = "#{plan['name']} (current)"
2603
+ # current_plan_name = plan['name']
2604
+ # end
2574
2605
  end
2575
- plan_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'servicePlan', 'type' => 'select', 'fieldLabel' => 'Plan', 'selectOptions' => service_plans_dropdown, 'required' => true, 'description' => 'Choose the appropriately sized plan for this instance'}],options[:options])
2606
+ plan_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'servicePlan', 'type' => 'select', 'fieldLabel' => 'Plan', 'selectOptions' => service_plans_dropdown, 'required' => true, 'defaultValue' => current_plan_name, 'description' => 'Choose the appropriately sized plan for this instance'}],options[:options])
2576
2607
  service_plan = service_plans.find {|sp| sp["id"] == plan_prompt['servicePlan'].to_i }
2577
2608
  new_plan_id = service_plan["id"]
2578
2609
  #payload[:servicePlan] = new_plan_id # ew, this api uses servicePlanId instead
2579
2610
  #payload[:servicePlanId] = new_plan_id
2580
- payload[:instance][:plan] = {id: service_plan["id"]}
2611
+ payload["instance"]["plan"] = {"id" => service_plan["id"]}
2581
2612
 
2582
2613
  volumes_response = @instances_interface.volumes(instance['id'])
2583
2614
  current_volumes = volumes_response['volumes'].sort {|x,y| x['displayOrder'] <=> y['displayOrder'] }
@@ -2585,29 +2616,27 @@ class Morpheus::Cli::Instances
2585
2616
  # prompt for volumes
2586
2617
  volumes = prompt_resize_volumes(current_volumes, service_plan, options)
2587
2618
  if !volumes.empty?
2588
- payload[:volumes] = volumes
2619
+ payload["volumes"] = volumes
2589
2620
  end
2590
2621
 
2591
2622
  # only amazon supports this option
2592
2623
  # for now, always do this
2593
- payload[:deleteOriginalVolumes] = true
2594
- @instances_interface.setopts(options)
2595
- if options[:dry_run]
2596
- print_dry_run @instances_interface.dry.resize(instance['id'], payload)
2597
- return
2598
- end
2599
- json_response = @instances_interface.resize(instance['id'], payload)
2600
- if options[:json]
2601
- puts as_json(json_response, options)
2602
- return 0
2603
- else
2604
- print_green_success "Resizing instance #{instance['name']}"
2605
- #list([])
2606
- end
2607
- rescue RestClient::Exception => e
2608
- print_rest_exception(e, options)
2609
- exit 1
2624
+ payload["deleteOriginalVolumes"] = true
2610
2625
  end
2626
+ payload.delete("rootVolume")
2627
+ (1..20).each {|i| payload.delete("dataVolume#{i}") }
2628
+ @instances_interface.setopts(options)
2629
+ if options[:dry_run]
2630
+ print_dry_run @instances_interface.dry.resize(instance['id'], payload)
2631
+ return
2632
+ end
2633
+ json_response = @instances_interface.resize(instance['id'], payload)
2634
+ render_response(json_response, options, 'snapshots') do
2635
+ print_green_success "Resizing instance #{instance['name']}"
2636
+ end
2637
+ return 0, nil
2638
+
2639
+
2611
2640
  end
2612
2641
 
2613
2642
  def backup(args)
@@ -2,12 +2,12 @@ require 'morpheus/cli/cli_command'
2
2
 
3
3
  class Morpheus::Cli::IntegrationsCommand
4
4
  include Morpheus::Cli::CliCommand
5
- include Morpheus::Cli::AccountsHelper
6
5
 
7
6
  set_command_name :'integrations'
8
7
  set_command_description "Integrations: View and manage integrations"
9
8
 
10
9
  register_subcommands :list, :get, :add, :update, :remove, :refresh
10
+ register_subcommands :list_objects, :get_object, :add_object, :remove_object
11
11
  register_subcommands :list_types, :get_type
12
12
 
13
13
  def connect(opts)
@@ -78,6 +78,9 @@ class Morpheus::Cli::IntegrationsCommand
78
78
  options = {}
79
79
  optparse = Morpheus::Cli::OptionParser.new do |opts|
80
80
  opts.banner = subcommand_usage("[integration]")
81
+ opts.on('--objects', 'Display exposed objects for the integration.') do
82
+ options[:show_objects] = true
83
+ end
81
84
  build_standard_get_options(opts, options)
82
85
  opts.footer = <<-EOT
83
86
  Get details about a specific integration.
@@ -101,6 +104,9 @@ EOT
101
104
  return 1, "integration not found for #{id}" if integration.nil?
102
105
  id = integration['id']
103
106
  end
107
+ if options[:show_objects]
108
+ params['objects'] = true
109
+ end
104
110
  @integrations_interface.setopts(options)
105
111
  if options[:dry_run]
106
112
  print_dry_run @integrations_interface.dry.get(id, params)
@@ -137,6 +143,74 @@ EOT
137
143
  show_columns.delete("Service Key") if integration['serviceKey'].nil?
138
144
  show_columns.delete("Auth Key") if integration['authKey'].nil?
139
145
  print_description_list(show_columns, integration, options)
146
+
147
+ if options[:show_objects]
148
+ # they are loaded above with ?objects=true
149
+ integration_objects = integration['objects']
150
+ if integration_objects.nil?
151
+ objects_json_response = @integrations_interface.list_objects(integration['id'], {})
152
+ integration_objects = objects_json_response[integration_object_list_key]
153
+ end
154
+ cloud_objects = integration_objects.select {|it| it['refType'] == "ComputeZone" }
155
+ library_objects = integration_objects.select {|it| it['refType'] == "InstanceTypeLayout" || it['refType'] == "InstanceType" }
156
+ blueprint_objects = integration_objects.select {|it| it['refType'] == "AppTemplate" }
157
+ catalog_objects = integration_objects.select {|it| it['refType'] == "CatalogItemType" }
158
+ if integration_objects.empty?
159
+ print reset,"\n"
160
+ print cyan,"No objects found.",reset,"\n"
161
+ else
162
+ # Exposed Clouds
163
+ if !cloud_objects.empty?
164
+ print_h2 "Exposed Clouds", [], options
165
+ list_columns = {
166
+ # "ID" => 'id',
167
+ "Name" => 'name',
168
+ # "Category" => 'category',
169
+ # "Ref Type" => 'refType',
170
+ # "Cloud ID" => 'refId',
171
+ "Group" => lambda {|it| it['group']['name'] rescue nil },
172
+ }.upcase_keys!
173
+ print as_pretty_table(cloud_objects, list_columns, options)
174
+ end
175
+
176
+ # Exposed Libraries
177
+ if !library_objects.empty?
178
+ # print_h2 "Exposed Libraries", [], options
179
+ print_h2 "Exposed Layouts", [], options
180
+ list_columns = {
181
+ # "ID" => 'id',
182
+ "Name" => 'name',
183
+ "Version" => lambda {|it| it['layout']['instanceVersion'] rescue nil },
184
+ "Instance Type" => lambda {|it| it['layout']['instanceType']['name'] rescue nil },
185
+ "Provision Type" => lambda {|it| it['layout']['provisionType']['name'] rescue nil },
186
+ }.upcase_keys!
187
+ print as_pretty_table(library_objects, list_columns, options)
188
+ end
189
+
190
+ # Exposed Blueprints
191
+ if !blueprint_objects.empty?
192
+ print_h2 "Exposed Blueprints", [], options
193
+ list_columns = {
194
+ # "ID" => 'id',
195
+ "Name" => 'name',
196
+ # "Type" => lambda {|it| it['blueprint']['type'] rescue nil },
197
+ "Blueprint" => lambda {|it| it['blueprint']['name'] rescue nil },
198
+ "Group" => lambda {|it| it['group']['name'] rescue nil },
199
+ }.upcase_keys!
200
+ print as_pretty_table(blueprint_objects, list_columns, options)
201
+ end
202
+
203
+ # Exposed Catalog Items
204
+ if !catalog_objects.empty?
205
+ print_h2 "Exposed Catalog Items", [], options
206
+ list_columns = {
207
+ # "ID" => 'id',
208
+ "Name" => 'name',
209
+ }.upcase_keys!
210
+ print as_pretty_table(catalog_objects, list_columns, options)
211
+ end
212
+ end
213
+ end
140
214
  print reset,"\n"
141
215
  end
142
216
  return 0, nil
@@ -459,6 +533,409 @@ EOT
459
533
  return 0, nil
460
534
  end
461
535
 
536
+ ## Integration Objects
537
+
538
+ def list_objects(args)
539
+ options = {}
540
+ params = {}
541
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
542
+ opts.banner = subcommand_usage("[integration] [search]")
543
+ opts.on('-t', '--type CODE', "Filter by types: cloud, layout, blueprint, catalog") do |val|
544
+ params['type'] = [params['type'], val].compact.flatten.collect {|it| it.to_s.strip.split(",") }.flatten.collect {|it| it.to_s.strip }
545
+ end
546
+ build_standard_list_options(opts, options)
547
+ opts.footer = <<-EOT
548
+ List integration objects.
549
+ [integration] is required. This is the name or id of an integration.
550
+ EOT
551
+ end
552
+ optparse.parse!(args)
553
+ verify_args!(args:args, optparse:optparse, min:1)
554
+ connect(options)
555
+
556
+ integration = find_integration_by_name_or_id(args[0])
557
+ return 1, "integration not found for #{args[0]}" if integration.nil?
558
+
559
+ if args.count > 1
560
+ options[:phrase] = args[1..-1].join(" ")
561
+ end
562
+ params.merge!(parse_list_options(options))
563
+ @integrations_interface.setopts(options)
564
+ if options[:dry_run]
565
+ print_dry_run @integrations_interface.dry.list_objects(integration['id'], params)
566
+ return 0, nil
567
+ end
568
+ json_response = @integrations_interface.list_objects(integration['id'], params)
569
+ render_response(json_response, options, integration_list_key) do
570
+ integration_objects = json_response[integration_object_list_key]
571
+ print_h1 "Integration Objects [#{integration['name']}]", parse_list_subtitles(options), options
572
+ if integration_objects.empty?
573
+ print cyan,"No objects found.",reset,"\n"
574
+ else
575
+ list_columns = {
576
+ "ID" => 'id',
577
+ "Name" => 'name',
578
+ # "Category" => 'category',
579
+ # "Ref Type" => 'refType',
580
+ # "Ref ID" => 'refId',
581
+ # "Type" => lambda {|it| it['type'] },
582
+ "Type" => lambda {|it| it['type'].to_s.capitalize },
583
+ "Ref ID" => 'refId',
584
+ }.upcase_keys!
585
+ print as_pretty_table(integration_objects, list_columns, options)
586
+ print_results_pagination(json_response)
587
+ end
588
+ print reset,"\n"
589
+ end
590
+ return 0, nil
591
+ end
592
+
593
+ def get_object(args)
594
+ params = {}
595
+ options = {}
596
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
597
+ opts.banner = subcommand_usage("[integration] [object]")
598
+ opts.on( '-c', '--config', "Display config only, for blueprint objects" ) do
599
+ options[:show_config] = true
600
+ end
601
+ build_standard_get_options(opts, options)
602
+ opts.footer = <<-EOT
603
+ Get details about a specific integration object.
604
+ [integration] is required. This is the name or id of an integration.
605
+ [object] is required. This is the name or id of an integration object.
606
+ EOT
607
+ end
608
+ optparse.parse!(args)
609
+ verify_args!(args:args, optparse:optparse, min:1)
610
+ connect(options)
611
+ optparse.parse!(args)
612
+ verify_args!(args:args, optparse:optparse, min:2)
613
+ connect(options)
614
+ integration = find_integration_by_name_or_id(args[0])
615
+ return 1, "integration not found for #{args[0]}" if integration.nil?
616
+ params.merge!(parse_query_options(options))
617
+ id_list = parse_id_list(args[1..-1])
618
+ return run_command_for_each_arg(id_list) do |arg|
619
+ _get_object(integration, arg, params, options)
620
+ end
621
+ end
622
+
623
+ def _get_object(integration, id, params, options)
624
+ integration_object = nil
625
+ if id.to_s !~ /\A\d{1,}\Z/
626
+ integration_object = find_integration_object_by_name_or_id(integration['id'], id)
627
+ return 1, "integration object not found for #{id}" if integration_object.nil?
628
+ id = integration_object['id']
629
+ end
630
+ @integrations_interface.setopts(options)
631
+ if options[:dry_run]
632
+ print_dry_run @integrations_interface.dry.get_object(integration['id'], id, params)
633
+ return
634
+ end
635
+ json_response = @integrations_interface.get_object(integration['id'], id, params)
636
+ integration_object = json_response[integration_object_object_key]
637
+ config = integration_object['config']
638
+ # export just the config as json (default) or yaml
639
+ if options[:show_config]
640
+ unless options[:json] || options[:yaml] || options[:csv]
641
+ options[:json] = :true
642
+ end
643
+ return render_with_format(config, options)
644
+ end
645
+ render_response(json_response, options, integration_object_object_key) do
646
+ print_h1 "Integration Object Details", [], options
647
+ print cyan
648
+ if integration_object['type'] == 'cloud'
649
+ show_columns = {
650
+ "Integration" => lambda {|it| integration['name'] },
651
+ "Object ID" => 'id',
652
+ "Name" => 'name',
653
+ "Type" => lambda {|it| it['type'].to_s.capitalize },
654
+ # "Cloud" => lambda {|it| it['cloud']['name'] rescue nil },
655
+ # "Ref Type" => 'refType',
656
+ "Ref ID" => 'refId',
657
+ # "Reference" => lambda {|it| ("#{it['refType']}: #{it['refId']}" rescue nil) },
658
+ "Group" => lambda {|it| it['group']['name'] rescue nil },
659
+ }
660
+ print_description_list(show_columns, integration_object, options)
661
+ print reset,"\n"
662
+ elsif integration_object['type'] == 'layout'
663
+ show_columns = {
664
+ "Integration" => lambda {|it| integration['name'] },
665
+ "Object ID" => 'id',
666
+ "Name" => 'name',
667
+ "Type" => lambda {|it| it['type'].to_s.capitalize },
668
+ # "Layout" => lambda {|it| it['layout']['name'] rescue nil },
669
+ # "Ref Type" => 'refType',
670
+ "Ref ID" => 'refId',
671
+ # "Reference" => lambda {|it| ("#{it['refType']}: #{it['refId']}" rescue nil) },
672
+ "Provision Type" => lambda {|it| it['layout']['provisionType']['name'] rescue nil },
673
+ "Instance Type" => lambda {|it| it['layout']['instanceType']['name'] rescue nil },
674
+ "Version" => lambda {|it| it['layout']['instanceVersion'] rescue nil },
675
+ }
676
+ print_description_list(show_columns, integration_object, options)
677
+ print reset,"\n"
678
+ elsif integration_object['type'] == 'blueprint'
679
+ show_columns = {
680
+ "Integration" => lambda {|it| integration['name'] },
681
+ "Object ID" => 'id',
682
+ "Name" => 'name',
683
+ "Type" => lambda {|it| it['type'].to_s.capitalize },
684
+ # "Ref Type" => 'refType',
685
+ "Ref ID" => 'refId',
686
+ # "Reference" => lambda {|it| ("#{it['refType']}: #{it['refId']}" rescue nil) },
687
+ # "Blueprint Type" => lambda {|it| it['blueprint']['type'] rescue nil },
688
+ "Blueprint" => lambda {|it| it['blueprint']['name'] rescue nil },
689
+ "Group" => lambda {|it| it['group']['name'] rescue nil },
690
+ "Default Cloud" => lambda {|it| it['defaultCloud']['name'] rescue nil },
691
+ "Environment" => lambda {|it| it['environment'] rescue nil },
692
+ }
693
+ print_description_list(show_columns, integration_object, options)
694
+ # print reset,"\n"
695
+ # print_h2 "App Spec"
696
+ print_h2 "Config"
697
+ if config
698
+ # config_string = integration_object['config'] || ""
699
+ config_string = config.is_a?(Hash) ? JSON.pretty_generate(config) : config.to_s
700
+ #print reset,config_string,"\n",reset
701
+ config_lines = config_string.split("\n")
702
+ config_line_count = config_lines.size
703
+ max_lines = 10
704
+ if config_lines.size > max_lines
705
+ config_string = config_lines.first(max_lines).join("\n")
706
+ config_string << "\n\n"
707
+ config_string << "#{dark}(#{(config_line_count - max_lines)} more lines were not shown, use -c to show the config)#{reset}"
708
+ #config_string << "\n"
709
+ end
710
+ # strip --- yaml header
711
+ if config_string[0..3] == "---\n"
712
+ config_string = config_string[4..-1]
713
+ end
714
+ print reset,config_string.chomp("\n"),"\n",reset
715
+ else
716
+ print reset,"(blank)","\n",reset
717
+ end
718
+ print reset,"\n"
719
+ elsif integration_object['type'] == 'catalog'
720
+ show_columns = {
721
+ "Integration" => lambda {|it| integration['name'] },
722
+ "Object ID" => 'id',
723
+ "Name" => 'name',
724
+ "Type" => lambda {|it| it['type'].to_s.capitalize },
725
+ "Catalog Item" => lambda {|it| it['catalogItemType']['name'] rescue nil },
726
+ # "Ref Type" => 'refType',
727
+ # "Ref ID" => 'refId',
728
+ # "Reference" => lambda {|it| ("#{it['refType']}: #{it['refId']}" rescue nil) },
729
+ }
730
+ print_description_list(show_columns, integration_object, options)
731
+ print reset,"\n"
732
+ else
733
+ # Unknown type?
734
+ show_columns = {
735
+ "Integration" => lambda {|it| integration['name'] },
736
+ "Object ID" => 'id',
737
+ "Name" => 'name',
738
+ "Type" => lambda {|it| it['type'].to_s.capitalize },
739
+ "Ref Type" => 'refType',
740
+ "Ref ID" => 'refId',
741
+ }
742
+ print_description_list(show_columns, integration_object, options)
743
+ print reset,"\n"
744
+ end
745
+ end
746
+ return 0, nil
747
+ end
748
+
749
+ def add_object(args)
750
+ options = {}
751
+ params = {}
752
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
753
+ opts.banner = subcommand_usage("[integration] [name] -t CODE [options]")
754
+ # opts.on('-t', '--type CODE', "Integration ObjectType code, see `#{command_name} list-types` for available type codes") do |val|
755
+ # options[:options]['type'] = val
756
+ # end
757
+ build_option_type_options(opts, options, add_integration_object_option_types)
758
+ opts.on('--config-file FILE', String, "Config from a local JSON or YAML file") do |val|
759
+ options[:config_file] = val.to_s
760
+ file_content = nil
761
+ full_filename = File.expand_path(options[:config_file])
762
+ if File.exists?(full_filename)
763
+ file_content = File.read(full_filename)
764
+ else
765
+ print_red_alert "File not found: #{full_filename}"
766
+ return 1
767
+ end
768
+ parse_result = parse_json_or_yaml(file_content)
769
+ config_map = parse_result[:data]
770
+ if config_map.nil?
771
+ # todo: bubble up JSON.parse error message
772
+ raise_command_error "Failed to parse config as YAML or JSON. Error: #{parse_result[:err]}"
773
+ #raise_command_error "Failed to parse config as valid YAML or JSON."
774
+ else
775
+ params['config'] = config_map
776
+ options[:options]['config'] = params['config'] # or file_content
777
+ end
778
+ end
779
+ # build_option_type_options(opts, options, add_integration_object_advanced_option_types)
780
+ build_standard_add_options(opts, options)
781
+ opts.footer = <<-EOT
782
+ Create a new integration object.
783
+ [integration] is required. This is the name or id of an integration.
784
+ [name] is required. This is the name of the new integration
785
+ Configuration options vary by integration type.
786
+ EOT
787
+ end
788
+ optparse.parse!(args)
789
+ verify_args!(args:args, optparse:optparse, min:1, max:2)
790
+ options[:options]['name'] = args[1] if args[1]
791
+ connect(options)
792
+ integration = find_integration_by_name_or_id(args[0])
793
+ return 1, "integration not found for #{args[0]}" if integration.nil?
794
+ payload = {}
795
+ if options[:payload]
796
+ payload = options[:payload]
797
+ payload.deep_merge!({integration_object_object_key => parse_passed_options(options)})
798
+ else
799
+ payload.deep_merge!({integration_object_object_key => parse_passed_options(options)})
800
+ v_prompt = Morpheus::Cli::OptionTypes.prompt(add_integration_object_option_types(), options[:options], @api_client, options[:params])
801
+ v_prompt.deep_compact!
802
+ params.deep_merge!(v_prompt)
803
+ advanced_config = Morpheus::Cli::OptionTypes.no_prompt(add_integration_object_advanced_option_types, options[:options], @api_client, options[:params])
804
+ advanced_config.deep_compact!
805
+ params.deep_merge!(advanced_config)
806
+ params.booleanize!
807
+
808
+ # convert config string to a map
809
+ # config = params['config']
810
+ # if config && config.is_a?(String)
811
+ # parse_result = parse_json_or_yaml(config)
812
+ # config_map = parse_result[:data]
813
+ # if config_map.nil?
814
+ # # todo: bubble up JSON.parse error message
815
+ # raise_command_error "Failed to parse config as YAML or JSON. Error: #{parse_result[:err]}"
816
+ # #raise_command_error "Failed to parse config as valid YAML or JSON."
817
+ # else
818
+ # params['config'] = config_map
819
+ # end
820
+ # end
821
+ # if params['config']
822
+ # config_map = params.delete('config')
823
+ # params['config'] = as_json(config_map, {:pretty_json => true})
824
+ # end
825
+ # if options[:interactive_config]
826
+ # print_h2 "App Config"
827
+ # config_map = prompt_app_config(options)
828
+ # params['config'] = config_map
829
+ # end
830
+
831
+ payload[integration_object_object_key].deep_merge!(params)
832
+ end
833
+ @integrations_interface.setopts(options)
834
+ if options[:dry_run]
835
+ print_dry_run @integrations_interface.dry.create_object(integration['id'], payload)
836
+ return 0, nil
837
+ end
838
+ json_response = @integrations_interface.create_object(integration['id'], payload)
839
+ integration_object = json_response[integration_object_object_key]
840
+ render_response(json_response, options, integration_object_object_key) do
841
+ print_green_success "Added integration_object #{integration_object['name']}"
842
+ return _get_object(integration, integration_object["id"], {}, options)
843
+ end
844
+ return 0, nil
845
+ end
846
+
847
+ # def update_object(args)
848
+ # options = {}
849
+ # params = {}
850
+ # optparse = Morpheus::Cli::OptionParser.new do |opts|
851
+ # opts.banner = subcommand_usage("[integration] [object] [options]")
852
+ # build_option_type_options(opts, options, update_integration_option_types)
853
+ # build_option_type_options(opts, options, update_integration_advanced_option_types)
854
+ # build_standard_update_options(opts, options)
855
+ # opts.footer = <<-EOT
856
+ # Update an integration.
857
+ # [integration] is required. This is the name or id of an integration.
858
+ # [object] is required. This is the name or id of an integration object.
859
+ # EOT
860
+ # end
861
+ # optparse.parse!(args)
862
+ # verify_args!(args:args, optparse:optparse, count:2)
863
+ # connect(options)
864
+ # integration = find_integration_by_name_or_id(args[0])
865
+ # return 1, "integration not found for #{args[0]}" if integration.nil?
866
+ # integration_object = find_integration_object_by_name_or_id(integration['id'], args[1])
867
+ # return 1, "integration object not found for #{args[1]}" if integration_object.nil?
868
+ # payload = {}
869
+ # if options[:payload]
870
+ # payload = options[:payload]
871
+ # payload.deep_merge!({integration_object_object_key => parse_passed_options(options)})
872
+ # else
873
+ # payload.deep_merge!({integration_object_object_key => parse_passed_options(options)})
874
+ # # do not prompt on update
875
+ # v_prompt = Morpheus::Cli::OptionTypes.no_prompt(update_integration_object_option_types, options[:options], @api_client, options[:params])
876
+ # v_prompt.deep_compact!
877
+ # params.deep_merge!(v_prompt)
878
+ # advanced_config = Morpheus::Cli::OptionTypes.no_prompt(update_integration_object_advanced_option_types, options[:options], @api_client, options[:params])
879
+ # advanced_config.deep_compact!
880
+ # params.deep_merge!(advanced_config)
881
+ # # convert checkbox "on" and "off" to true and false
882
+ # params.booleanize!
883
+ # # massage association params a bit
884
+
885
+ # payload.deep_merge!({integration_object_object_key => params})
886
+ # if payload[integration_object_object_key].empty? # || options[:no_prompt]
887
+ # raise_command_error "Specify at least one option to update.\n#{optparse}"
888
+ # end
889
+ # end
890
+ # @integrations_interface.setopts(options)
891
+ # if options[:dry_run]
892
+ # print_dry_run @integrations_interface.dry.update_object(integration['id'], integration_object['id'], payload)
893
+ # return
894
+ # end
895
+ # json_response = @integrations_interface.update_object(integration['id'], integration_object['id'], payload)
896
+ # integration_object = json_response[integration_object_object_key]
897
+ # render_response(json_response, options, integration_object_object_key) do
898
+ # print_green_success "Updated integration object #{integration_object['name']}"
899
+ # return _get_object(integration, integration_object["id"], {}, options)
900
+ # end
901
+ # return 0, nil
902
+ # end
903
+
904
+ def remove_object(args)
905
+ options = {}
906
+ params = {}
907
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
908
+ opts.banner = subcommand_usage("[integration] [options]")
909
+ build_standard_remove_options(opts, options)
910
+ opts.footer = <<-EOT
911
+ Delete an integration object.
912
+ [integration] is required. This is the name or id of an integration.
913
+ [object] is required. This is the name or id of an integration object.
914
+ EOT
915
+ end
916
+ optparse.parse!(args)
917
+ verify_args!(args:args, optparse:optparse, count:2)
918
+ connect(options)
919
+ integration = find_integration_by_name_or_id(args[0])
920
+ return 1, "integration not found for #{args[0]}" if integration.nil?
921
+ integration_object = find_integration_object_by_name_or_id(integration['id'], args[1])
922
+ return 1, "integration object not found for #{args[1]}" if integration_object.nil?
923
+ params.merge!(parse_query_options(options))
924
+ @integrations_interface.setopts(options)
925
+ if options[:dry_run]
926
+ print_dry_run @integrations_interface.dry.destroy_object(integration['id'], integration_object['id'], params)
927
+ return
928
+ end
929
+ unless options[:yes] || Morpheus::Cli::OptionTypes.confirm("Are you sure you want to delete the integration object #{integration_object['name']}?")
930
+ return 9, "aborted command"
931
+ end
932
+ json_response = @integrations_interface.destroy_object(integration['id'], integration_object['id'], params)
933
+ render_response(json_response, options) do
934
+ print_green_success "Removed integration object #{integration_object['name']}"
935
+ end
936
+ return 0, nil
937
+ end
938
+
462
939
  private
463
940
 
464
941
  def format_integration_type(integration)
@@ -641,4 +1118,93 @@ EOT
641
1118
  end
642
1119
  end
643
1120
 
1121
+ ## Integration Object helpers
1122
+
1123
+ def add_integration_object_option_types
1124
+ [
1125
+ {'code' => 'integrationObject.type', 'shorthand' => '-t', 'switch' => 'type', 'fieldName' => 'type', 'fieldLabel' => 'Type', 'type' => 'select', 'optionSource' => 'integrationObjectTypes', 'required' => true, 'description' => "Integration Object Type eg. cloud, layout, blueprint, catalog", 'displayOrder' => 1},
1126
+ # {'fieldName' => 'name', 'fieldLabel' => 'Name', 'type' => 'text', 'required' => false, 'description' => 'Display Name of the integration object, default is the name of the referenced object', 'displayOrder' => 2},
1127
+ {'dependsOnCode' => 'integrationObject.type:cloud', 'switch' => 'group', 'fieldName' => 'group', 'fieldLabel' => 'Group', 'type' => 'select', 'optionSource' => 'groups', 'required' => true, 'description' => 'Group', 'displayOrder' => 3},
1128
+ {'dependsOnCode' => 'integrationObject.type:cloud', 'switch' => 'cloud', 'fieldName' => 'cloud', 'fieldLabel' => 'Cloud', 'type' => 'select', 'optionSource' => 'clouds', 'required' => true, 'description' => 'Cloud', 'displayOrder' => 4},
1129
+ {'dependsOnCode' => 'integrationObject.type:layout', 'switch' => 'instance-type', 'fieldName' => 'instanceType', 'fieldLabel' => 'Instance Type', 'type' => 'select', 'optionSource' => 'instanceTypes', 'required' => true, 'description' => 'Instance Type', 'displayOrder' => 5},
1130
+ {'dependsOnCode' => 'integrationObject.type:layout', 'switch' => 'technology', 'fieldName' => 'zoneType', 'fieldLabel' => 'Cloud Type', 'type' => 'select', 'optionSource' => 'zoneTypes', 'required' => true, 'description' => 'Cloud Type (Technology)', 'displayOrder' => 5},
1131
+ {'dependsOnCode' => 'integrationObject.type:layout', 'switch' => 'layout', 'fieldName' => 'layout', 'fieldLabel' => 'Layout', 'type' => 'select', 'optionSource' => 'layouts', 'required' => true, 'description' => 'Layout', 'displayOrder' => 6},
1132
+ {'dependsOnCode' => 'integrationObject.type:blueprint', 'fieldName' => 'name', 'fieldLabel' => 'Catalog Item Name', 'type' => 'text', 'required' => true, 'description' => 'Display Name of the integration object', 'displayOrder' => 7},
1133
+ {'dependsOnCode' => 'integrationObject.type:blueprint', 'switch' => 'blueprint', 'fieldName' => 'blueprint', 'fieldLabel' => 'Blueprint', 'type' => 'select', 'optionSource' => 'blueprints', 'required' => true, 'description' => 'Blueprint', 'displayOrder' => 8, 'noParams' => true},
1134
+ {'dependsOnCode' => 'integrationObject.type:blueprint', 'switch' => 'group', 'fieldName' => 'group', 'fieldLabel' => 'Group', 'type' => 'select', 'optionSource' => 'groups', 'required' => true, 'description' => 'Group', 'displayOrder' => 9},
1135
+ {'dependsOnCode' => 'integrationObject.type:blueprint', 'switch' => 'default-cloud', 'fieldName' => 'defaultCloud', 'fieldLabel' => 'Default Cloud', 'type' => 'select', 'optionSource' => 'clouds', 'required' => false, 'description' => 'Default Cloud', 'displayOrder' => 10},
1136
+ {'dependsOnCode' => 'integrationObject.type:blueprint', 'switch' => 'environment', 'fieldName' => 'environment', 'fieldLabel' => 'Environment', 'type' => 'select', 'optionSource' => 'environments', 'required' => false, 'description' => 'Environment', 'displayOrder' => 11},
1137
+ {'dependsOnCode' => 'integrationObject.type:blueprint', 'switch' => 'config', 'fieldName' => 'config', 'fieldLabel' => 'Config', 'type' => 'code-editor', 'required' => true, 'description' => 'Config JSON', 'displayOrder' => 12},
1138
+ {'dependsOnCode' => 'integrationObject.type:catalog', 'switch' => 'catalog', 'fieldName' => 'catalog', 'fieldLabel' => 'Catalog Item', 'type' => 'select', 'optionSource' => 'catalogItemTypes', 'required' => true, 'description' => 'Catalog Item', 'displayOrder' => 13},
1139
+ ]
1140
+ end
1141
+
1142
+ def add_integration_object_advanced_option_types
1143
+ []
1144
+ end
1145
+
1146
+ def update_integration_object_option_types
1147
+ list = add_integration_object_option_types.collect {|it|
1148
+ it.delete('required')
1149
+ it.delete('defaultValue')
1150
+ it
1151
+ }
1152
+ list = list.reject {|it| ["type"].include? it['fieldName'] }
1153
+ list
1154
+ end
1155
+
1156
+ def update_integration_object_advanced_option_types
1157
+ add_integration_advanced_option_types.collect {|it|
1158
+ it.delete('required')
1159
+ it.delete('defaultValue')
1160
+ it
1161
+ }
1162
+ end
1163
+
1164
+ def integration_object_object_key
1165
+ 'object'
1166
+ end
1167
+
1168
+ def integration_object_list_key
1169
+ 'objects'
1170
+ end
1171
+
1172
+ def find_integration_object_by_name_or_id(integration_id, val)
1173
+ if val.to_s =~ /\A\d{1,}\Z/
1174
+ return find_integration_object_by_id(integration_id, val)
1175
+ else
1176
+ return find_integration_object_by_name(integration_id, val)
1177
+ end
1178
+ end
1179
+
1180
+ def find_integration_object_by_id(integration_id, id)
1181
+ begin
1182
+ json_response = @integrations_interface.get_object(integration_id, id.to_i)
1183
+ return json_response[integration_object_object_key]
1184
+ rescue RestClient::Exception => e
1185
+ if e.response && e.response.code == 404
1186
+ print_red_alert "integration object not found by id '#{id}'"
1187
+ else
1188
+ raise e
1189
+ end
1190
+ end
1191
+ end
1192
+
1193
+ def find_integration_object_by_name(integration_id, name)
1194
+ json_response = @integrations_interface.list_objects(integration_id, {name: name.to_s})
1195
+ integration_objects = json_response[integration_object_list_key]
1196
+ if integration_objects.empty?
1197
+ print_red_alert "integration object not found by name '#{name}'"
1198
+ return nil
1199
+ elsif integration_objects.size > 1
1200
+ print_red_alert "#{integration_objects.size} integration object found by name '#{name}'"
1201
+ puts_error as_pretty_table(integration_objects, [:id, :name], {color:red})
1202
+ print_red_alert "Try using ID instead"
1203
+ print reset,"\n"
1204
+ return nil
1205
+ else
1206
+ return integration_objects[0]
1207
+ end
1208
+ end
1209
+
644
1210
  end