morpheus-cli 4.2.18 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +1 -1
  3. data/lib/morpheus/api/api_client.rb +14 -0
  4. data/lib/morpheus/api/billing_interface.rb +33 -0
  5. data/lib/morpheus/api/catalog_item_types_interface.rb +9 -0
  6. data/lib/morpheus/api/rest_interface.rb +0 -6
  7. data/lib/morpheus/api/roles_interface.rb +14 -0
  8. data/lib/morpheus/cli.rb +2 -2
  9. data/lib/morpheus/cli/apps.rb +3 -4
  10. data/lib/morpheus/cli/backup_jobs_command.rb +3 -0
  11. data/lib/morpheus/cli/backups_command.rb +3 -0
  12. data/lib/morpheus/cli/blueprints_command.rb +27 -61
  13. data/lib/morpheus/cli/budgets_command.rb +4 -4
  14. data/lib/morpheus/cli/catalog_command.rb +507 -0
  15. data/lib/morpheus/cli/cli_command.rb +30 -15
  16. data/lib/morpheus/cli/cloud_resource_pools_command.rb +16 -0
  17. data/lib/morpheus/cli/clouds.rb +7 -10
  18. data/lib/morpheus/cli/commands/standard/curl_command.rb +23 -7
  19. data/lib/morpheus/cli/commands/standard/source_command.rb +1 -1
  20. data/lib/morpheus/cli/commands/standard/update_command.rb +76 -0
  21. data/lib/morpheus/cli/containers_command.rb +14 -0
  22. data/lib/morpheus/cli/deployments.rb +1 -1
  23. data/lib/morpheus/cli/hosts.rb +30 -2
  24. data/lib/morpheus/cli/instances.rb +19 -1
  25. data/lib/morpheus/cli/invoices_command.rb +8 -9
  26. data/lib/morpheus/cli/jobs_command.rb +30 -8
  27. data/lib/morpheus/cli/library_instance_types_command.rb +17 -3
  28. data/lib/morpheus/cli/library_option_lists_command.rb +14 -6
  29. data/lib/morpheus/cli/mixins/accounts_helper.rb +7 -6
  30. data/lib/morpheus/cli/mixins/backups_helper.rb +2 -4
  31. data/lib/morpheus/cli/mixins/catalog_helper.rb +66 -0
  32. data/lib/morpheus/cli/mixins/deployments_helper.rb +0 -1
  33. data/lib/morpheus/cli/mixins/option_source_helper.rb +1 -1
  34. data/lib/morpheus/cli/mixins/print_helper.rb +46 -0
  35. data/lib/morpheus/cli/network_pools_command.rb +14 -6
  36. data/lib/morpheus/cli/ping.rb +0 -1
  37. data/lib/morpheus/cli/projects_command.rb +7 -7
  38. data/lib/morpheus/cli/provisioning_licenses_command.rb +2 -2
  39. data/lib/morpheus/cli/remote.rb +0 -2
  40. data/lib/morpheus/cli/roles.rb +305 -3
  41. data/lib/morpheus/cli/service_plans_command.rb +2 -2
  42. data/lib/morpheus/cli/storage_providers_command.rb +40 -56
  43. data/lib/morpheus/cli/tasks.rb +24 -10
  44. data/lib/morpheus/cli/tenants_command.rb +1 -1
  45. data/lib/morpheus/cli/usage_command.rb +150 -0
  46. data/lib/morpheus/cli/user_settings_command.rb +1 -0
  47. data/lib/morpheus/cli/users.rb +12 -1
  48. data/lib/morpheus/cli/version.rb +1 -1
  49. data/lib/morpheus/cli/workflows.rb +12 -9
  50. data/lib/morpheus/formatters.rb +26 -5
  51. metadata +8 -2
@@ -409,9 +409,9 @@ class Morpheus::Cli::ServicePlanCommand
409
409
  while Morpheus::Cli::OptionTypes.confirm("Add #{price_sets.empty? ? '' : 'another '}price set?", {:default => false}) do
410
410
  price_unit = prompt_price_unit(options)
411
411
 
412
- avail_price_sets ||= @price_sets_interface.list['priceSets'].collect {|it| {'name' => it['name'], 'value' => it['id'], 'priceUnit' => it['priceUnit']}}
412
+ avail_price_sets ||= @price_sets_interface.list({'priceUnit' => price_unit, 'max' => 10000})['priceSets'].collect {|it| {'name' => it['name'], 'value' => it['id'], 'priceUnit' => it['priceUnit']}}
413
413
 
414
- if price_set_id = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'priceSet', 'type' => 'select', 'fieldLabel' => 'Price Set', 'selectOptions' => avail_price_sets.reject {|it| it['priceUnit'] != price_unit}, 'required' => false, 'description' => 'Select Price.'}],options[:options],@api_client,{}, options[:no_prompt], true)['priceSet']
414
+ if price_set_id = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'priceSet', 'type' => 'select', 'fieldLabel' => 'Price Set', 'selectOptions' => avail_price_sets, 'required' => false, 'description' => 'Select Price.'}],options[:options],@api_client,{}, options[:no_prompt], true)['priceSet']
415
415
  price_set = avail_price_sets.find {|it| it['value'] == price_set_id}
416
416
  price_sets << {'id' => price_set['value'], 'priceUnit' => price_set['priceUnit']}
417
417
  avail_price_sets.reject! {|it| it['value'] == price_set_id}
@@ -42,71 +42,39 @@ class Morpheus::Cli::StorageProvidersCommand
42
42
  def list(args)
43
43
  options = {}
44
44
  params = {}
45
+ ref_ids = []
45
46
  optparse = Morpheus::Cli::OptionParser.new do |opts|
46
- opts.banner = subcommand_usage()
47
- build_common_options(opts, options, [:list, :json, :yaml, :csv, :fields, :json, :dry_run, :remote])
47
+ opts.banner = subcommand_usage("[search]")
48
+ build_standard_list_options(opts, options)
48
49
  opts.footer = "List storage buckets."
49
50
  end
50
51
  optparse.parse!(args)
51
- if args.count != 0
52
- print_error Morpheus::Terminal.angry_prompt
53
- puts_error "wrong number of arguments, expected 0 and got #{args.count}\n#{optparse}"
54
- return 1
55
- end
56
52
  connect(options)
57
- begin
58
- params.merge!(parse_list_options(options))
59
- @storage_providers_interface.setopts(options)
60
- if options[:dry_run]
61
- print_dry_run @storage_providers_interface.dry.list(params)
62
- return
63
- end
64
- json_response = @storage_providers_interface.list(params)
65
- storage_providers = json_response["storageBuckets"]
66
- if options[:json]
67
- puts as_json(json_response, options, "storageBuckets")
68
- return 0
69
- elsif options[:yaml]
70
- puts as_yaml(json_response, options, "storageBuckets")
71
- return 0
72
- elsif options[:csv]
73
- puts records_as_csv(storage_providers, options)
74
- return 0
75
- end
76
- title = "Morpheus Storage Buckets"
77
- subtitles = []
78
- subtitles += parse_list_subtitles(options)
79
- print_h1 title, subtitles
80
- if storage_providers.empty?
53
+ if args.count > 0
54
+ options[:phrase] = args.join(" ")
55
+ end
56
+ params.merge!(parse_list_options(options))
57
+ @storage_providers_interface.setopts(options)
58
+ if options[:dry_run]
59
+ print_dry_run @storage_providers_interface.dry.list(params)
60
+ return
61
+ end
62
+ json_response = @storage_providers_interface.list(params)
63
+ storage_buckets = json_response['storageBuckets']
64
+ render_response(json_response, options, 'storageBuckets') do
65
+ print_h1 "Morpheus Storage Buckets", parse_list_subtitles(options), options
66
+ if storage_buckets.empty?
81
67
  print cyan,"No storage buckets found.",reset,"\n"
82
68
  else
83
- rows = storage_providers.collect {|storage_provider|
84
- row = {
85
- id: storage_provider['id'],
86
- name: storage_provider['name'],
87
- provider: format_storage_provider_type(storage_provider),
88
- bucket: format_bucket_name(storage_provider),
89
- backups: storage_provider['defaultBackupTarget'] ? 'Yes' : 'No',
90
- deployments: storage_provider['defaultDeploymentTarget'] ? 'Yes' : 'No',
91
- virtualImages: storage_provider['defaultVirtualImageTarget'] ? 'Yes' : 'No',
92
- }
93
- row
94
- }
95
- columns = [:id, :name, {:provider => {:display_name => "Provider Type".upcase} }, {:bucket => {:display_name => "Bucket Name".upcase} }, :backups, :deployments]
96
- if options[:include_fields]
97
- columns = options[:include_fields]
98
- rows = storage_providers
99
- end
100
- print cyan
101
- print as_pretty_table(rows, columns, options)
102
- print reset
103
- print_results_pagination(json_response, {:label => "storage bucket", :n_label => "storage buckets"})
69
+ print as_pretty_table(storage_buckets, storage_bucket_column_definitions.upcase_keys!, options)
70
+ print_results_pagination(json_response)
104
71
  end
105
72
  print reset,"\n"
106
- return 0
107
- rescue RestClient::Exception => e
108
- print_rest_exception(e, options)
109
- exit 1
73
+ end
74
+ if storage_buckets.empty?
75
+ return 1, "no storage buckets found"
76
+ else
77
+ return 0, nil
110
78
  end
111
79
  end
112
80
 
@@ -1232,6 +1200,22 @@ class Morpheus::Cli::StorageProvidersCommand
1232
1200
 
1233
1201
  private
1234
1202
 
1203
+ def storage_bucket_column_definitions()
1204
+ {
1205
+ "ID" => 'id',
1206
+ "Name" => 'name',
1207
+ # "Description" => 'description',
1208
+ "Provider Type" => lambda {|it| format_storage_provider_type(it) },
1209
+ "Bucket Name" => lambda {|it| format_bucket_name(it) },
1210
+ # "Source" => lambda {|it| it['source'] },
1211
+ "Backups" => lambda {|it| format_boolean(it['defaultBackupTarget']) },
1212
+ "Deployments" => lambda {|it| format_boolean(it['defaultDeploymentTarget']) },
1213
+ "Virtual Images" => lambda {|it| format_boolean(it['defaultVirtualImageTarget']) },
1214
+ # "Archive Snapshots" => lambda {|it| format_boolean(it['copyToStore']) },
1215
+ # "Created" => lambda {|it| format_local_dt(it['dateCreated']) },
1216
+ # "Updated" => lambda {|it| format_local_dt(it['lastUpdated']) },
1217
+ }
1218
+ end
1235
1219
 
1236
1220
  def get_storage_provider_types()
1237
1221
  [
@@ -832,6 +832,7 @@ class Morpheus::Cli::Tasks
832
832
  payload = options[:payload]
833
833
  payload.deep_merge!({'job' => passed_options}) unless passed_options.empty?
834
834
  else
835
+ # always parse instances and/or hosts
835
836
  if instance_ids.size > 0 && server_ids.size > 0
836
837
  raise_command_error "Pass --instance or --host, not both.\n#{optparse}"
837
838
  elsif instance_ids.size > 0
@@ -848,19 +849,30 @@ class Morpheus::Cli::Tasks
848
849
  servers << server
849
850
  end
850
851
  params['servers'] = servers.collect {|it| it['id'] }
851
- elsif target_type == 'appliance'
852
- # cool, run it locally.
852
+ end
853
+ # validate requires inputs based on task executeTarget
854
+ if task['executeTarget'] == 'resource'
855
+ if instance_ids.empty? && server_ids.empty?
856
+ # todo: prompt for Context: None,Instance,Server and then Instance(s) or Server(s)
857
+ raise_command_error "missing required option: --instance or --host\n#{optparse}"
858
+ end
859
+ elsif task['executeTarget'] == 'local'
860
+ # no targetType required for local
861
+ elsif task['executeTarget'] == 'remote'
862
+ # not sure about this one
853
863
  else
854
- raise_command_error "missing required option: --instance or --host\n#{optparse}"
864
+ # unknown executeTarget
855
865
  end
866
+
867
+
856
868
 
857
869
  # todo: prompt to task optionTypes for customOptions
858
870
  if task['optionTypes']
859
871
 
860
872
  end
861
-
862
- params['targetType'] = target_type
863
-
873
+ if target_type
874
+ params['targetType'] = target_type
875
+ end
864
876
  job_payload = {}
865
877
  job_payload.deep_merge!(params)
866
878
  job_payload.deep_merge!(passed_options) unless passed_options.empty?
@@ -877,15 +889,17 @@ class Morpheus::Cli::Tasks
877
889
  puts as_json(json_response, options)
878
890
  return json_response['success'] ? 0 : 1
879
891
  else
880
- target_desc = ""
892
+ target_desc = nil
881
893
  if instances.size() > 0
882
894
  target_desc = (instances.size() == 1) ? "instance #{instances[0]['name']}" : "#{instances.size()} instances"
883
895
  elsif servers.size() > 0
884
896
  target_desc = (servers.size() == 1) ? "host #{servers[0]['name']}" : "#{servers.size()} hosts"
885
- elsif target_type == 'appliance'
886
- target_desc = "appliance"
887
897
  end
888
- print_green_success "Executing task #{task['name']} on #{target_desc}"
898
+ if target_desc
899
+ print_green_success "Executing task #{task['name']} on #{target_desc}"
900
+ else
901
+ print_green_success "Executing task #{task['name']}"
902
+ end
889
903
  # todo: refresh, use get processId and load process record isntead? err
890
904
  if json_response["jobExecution"] && json_response["jobExecution"]["id"]
891
905
  get_args = [json_response["jobExecution"]["id"], "--details"] + (options[:remote] ? ["-r",options[:remote]] : [])
@@ -295,7 +295,7 @@ EOT
295
295
  [
296
296
  {'fieldName' => 'name', 'fieldLabel' => 'Name', 'type' => 'text', 'required' => true, 'displayOrder' => 1},
297
297
  {'fieldName' => 'description', 'fieldLabel' => 'Description', 'type' => 'text', 'displayOrder' => 2},
298
- {'fieldContext' => 'role', 'fieldName' => 'id', 'fieldLabel' => 'Base Role', 'type' => 'select', 'optionSource' => lambda {
298
+ {'fieldContext' => 'role', 'fieldName' => 'id', 'fieldLabel' => 'Base Role', 'type' => 'select', 'optionSource' => lambda { |api_client, api_params|
299
299
  @roles_interface.list(nil, {roleType:'account'})['roles'].collect {|it|
300
300
  {"name" => (it["authority"] || it["name"]), "value" => it["id"]}
301
301
  }
@@ -0,0 +1,150 @@
1
+ require 'morpheus/cli/cli_command'
2
+
3
+ # CLI command usages
4
+ # UI is Costing - Usage
5
+ # API is /billing and returns usages
6
+ class Morpheus::Cli::UsageCommand
7
+ include Morpheus::Cli::CliCommand
8
+ include Morpheus::Cli::OptionSourceHelper
9
+
10
+ set_command_name :'usage'
11
+
12
+ register_subcommands :list #, :list_tenant, :list_clouds, :list_zones, :list_zones, :list_zones, :list_zones
13
+
14
+ def connect(opts)
15
+ @api_client = establish_remote_appliance_connection(opts)
16
+ @billing_interface = @api_client.billing
17
+ end
18
+
19
+ def handle(args)
20
+ handle_subcommand(args)
21
+ end
22
+
23
+ def list(args)
24
+ options = {}
25
+ params = {}
26
+ ref_ids = []
27
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
28
+ opts.banner = subcommand_usage("[search]")
29
+ opts.on( '-t', '--type TYPE', "Filter by type" ) do |val|
30
+ params['type'] = parse_usage_type(val)
31
+ end
32
+ opts.on( '-c', '--cloud CLOUD', "Filter by cloud" ) do |val|
33
+ options[:cloud] = val
34
+ end
35
+ opts.on('--start DATE', String, "Start date in the format YYYY-MM-DD.") do |val|
36
+ params['startDate'] = val #parse_time(val).utc.iso8601
37
+ end
38
+ opts.on('--end DATE', String, "End date in the format YYYY-MM-DD. Default is now.") do |val|
39
+ params['endDate'] = val #parse_time(val).utc.iso8601
40
+ end
41
+ opts.on('--sigdig DIGITS', "Significant digits when rounding cost values for display as currency. Default is 5.") do |val|
42
+ options[:sigdig] = val.to_i
43
+ end
44
+ build_standard_list_options(opts, options)
45
+ opts.footer = "List usages."
46
+ end
47
+ optparse.parse!(args)
48
+ connect(options)
49
+ # verify_args!(args:args, optparse:optparse, count:0)
50
+ if args.count > 0
51
+ options[:phrase] = args.join(" ")
52
+ end
53
+ params.merge!(parse_list_options(options))
54
+ # --cloud
55
+ if options[:cloud]
56
+ params['cloud'] = parse_id_list(options[:cloud]).collect {|cloud_id|
57
+ if cloud_id.to_s =~ /\A\d{1,}\Z/
58
+ cloud_id
59
+ else
60
+ cloud = find_cloud_option(cloud_id)
61
+ return 1 if cloud.nil?
62
+ cloud['id']
63
+ end
64
+ }
65
+ end
66
+
67
+ @billing_interface.setopts(options)
68
+ if options[:dry_run]
69
+ print_dry_run @billing_interface.dry.list(params)
70
+ return
71
+ end
72
+ json_response = @billing_interface.list(params)
73
+ usages = json_response[usage_list_key]
74
+ render_response(json_response, options, usage_list_key) do
75
+ print_h1 "Morpheus Usages", parse_list_subtitles(options), options
76
+ if usages.empty?
77
+ print cyan,"No usages found.",reset,"\n"
78
+ else
79
+ list_columns = {
80
+ "ID" => 'id',
81
+ "Cloud" => 'zoneName',
82
+ "Type" => lambda {|it| format_usage_type(it) },
83
+ "Name" => 'name',
84
+ "Plan" => 'planName',
85
+ "Start Date" => lambda {|it| format_local_dt(it['startDate']) },
86
+ "End Date" => lambda {|it| format_local_dt(it['endDate']) },
87
+ "Usage Status" => lambda {|it| format_usage_status(it) },
88
+ "Usage Price" => lambda {|it| format_money(it['price'], it['currency'] || 'USD', {sigdig: (options[:sigdig] || 5)}) },
89
+ }
90
+ print as_pretty_table(usages, list_columns.upcase_keys!, options)
91
+ print_results_pagination(json_response)
92
+ end
93
+ print reset,"\n"
94
+ end
95
+ if usages.empty?
96
+ return 1, "no usages found"
97
+ else
98
+ return 0, nil
99
+ end
100
+ end
101
+
102
+
103
+ private
104
+
105
+ def usage_object_key
106
+ 'usage'
107
+ end
108
+
109
+ def usage_list_key
110
+ 'usages'
111
+ end
112
+
113
+ def format_usage_type(usage)
114
+ #return usage['costDetails']['refType']
115
+ ref_type = usage['costDetails'] ? usage['costDetails']['refType'].to_s : ''
116
+ if ref_type == 'discoveredServer'
117
+ 'Discovered'
118
+ elsif ref_type == 'computeServer'
119
+ 'Host'
120
+ elsif ref_type == 'container'
121
+ 'Container'
122
+ else
123
+ ref_type.to_s
124
+ end
125
+ end
126
+
127
+ def parse_usage_type(val)
128
+ type_string = val.to_s.downcase
129
+ if type_string == 'discoveredServer'
130
+ 'discoveredServer'
131
+ elsif type_string == 'host'
132
+ 'computeServer'
133
+ elsif type_string == 'container'
134
+ 'container'
135
+ else
136
+ val
137
+ end
138
+ end
139
+
140
+ def format_usage_status(usage, return_color=cyan)
141
+ #return usage['status'].to_s.capitalize
142
+ status_string = usage['status'].to_s
143
+ if status_string == 'stopped'
144
+ return "#{cyan}#{status_string.upcase}#{return_color}"
145
+ else
146
+ return "#{cyan}#{status_string.upcase}#{return_color}"
147
+ end
148
+ end
149
+
150
+ end
@@ -94,6 +94,7 @@ EOT
94
94
  "Linux Key Pair" => lambda {|it| it['linuxKeyPairId'] },
95
95
  "Windows Username" => lambda {|it| it['windowsUsername'] },
96
96
  "Windows Password" => lambda {|it| it['windowsPassword'] },
97
+ "Default Persona" => lambda {|it| it['defaultPersona'] ? it['defaultPersona']['name'] : '' },
97
98
  }
98
99
  print_description_list(description_cols, user_settings)
99
100
 
@@ -161,12 +161,22 @@ class Morpheus::Cli::Users
161
161
  options[:include_app_templates_access] = true
162
162
  params['includeAccess'] = true
163
163
  end
164
+ opts.on(nil,'--catalog-item-type-access', "Display Catalog Item Type Access") do
165
+ options[:include_catalog_item_types_access] = true
166
+ params['includeAccess'] = true
167
+ end
168
+ opts.on(nil,'--personas', "Display Persona Access") do
169
+ options[:include_personas_access] = true
170
+ params['includeAccess'] = true
171
+ end
164
172
  opts.on(nil,'--all', "Display All Access Lists") do
165
173
  options[:include_features_access] = true
166
174
  options[:include_sites_access] = true
167
175
  options[:include_zones_access] = true
168
176
  options[:include_instance_types_access] = true
169
177
  options[:include_app_templates_access] = true
178
+ options[:include_catalog_item_types_access] = true
179
+ options[:include_personas_access] = true
170
180
  params['includeAccess'] = true
171
181
  end
172
182
  opts.on('-i', '--include-none-access', "Include Items with 'None' Access in Access List") do
@@ -241,7 +251,8 @@ EOT
241
251
  puts yellow,"No permissions found.",reset
242
252
  end
243
253
  else
244
- available_field_options = {'features' => 'Feature', 'sites' => 'Group', 'zones' => 'Cloud', 'instance_types' => 'Instance Type', 'app_templates' => 'Blueprint'}
254
+ available_field_options = {'features' => 'Feature', 'sites' => 'Group', 'zones' => 'Cloud', 'instance_types' => 'Instance Type',
255
+ 'app_templates' => 'Blueprint', 'catalog_item_types' => 'Catalog Item Types', 'personas' => 'Personas'}
245
256
  available_field_options.each do |field, label|
246
257
  if !(field == 'sites' && is_tenant_account) && options["include_#{field}_access".to_sym]
247
258
  access = user['access'][field.split('_').enum_for(:each_with_index).collect {|word, idx| idx == 0 ? word : word.capitalize}.join]
@@ -1,6 +1,6 @@
1
1
 
2
2
  module Morpheus
3
3
  module Cli
4
- VERSION = "4.2.18"
4
+ VERSION = "5.0.0"
5
5
  end
6
6
  end
@@ -619,10 +619,11 @@ class Morpheus::Cli::Workflows
619
619
  elsif target_type == 'appliance'
620
620
  # cool, run it locally.
621
621
  else
622
- raise_command_error "missing required option: --instance, --host or --appliance\n#{optparse}"
622
+ # cool, run it locally.
623
+ #raise_command_error "missing required option: --instance, --host or --appliance\n#{optparse}"
623
624
  end
624
625
 
625
- # todo: prompt to workflow optionTypes for customOptions
626
+ # prompt to workflow optionTypes for customOptions
626
627
  custom_options = nil
627
628
  if workflow['optionTypes'] && workflow['optionTypes'].size() > 0
628
629
  custom_option_types = workflow['optionTypes'].collect {|it|
@@ -631,9 +632,9 @@ class Morpheus::Cli::Workflows
631
632
  }
632
633
  custom_options = Morpheus::Cli::OptionTypes.prompt(custom_option_types, options[:options], @api_client, {})
633
634
  end
634
-
635
- params['targetType'] = target_type
636
-
635
+ if target_type
636
+ params['targetType'] = target_type
637
+ end
637
638
  job_payload = {}
638
639
  job_payload.deep_merge!(params)
639
640
  passed_options.delete('customOptions')
@@ -655,15 +656,17 @@ class Morpheus::Cli::Workflows
655
656
  puts as_json(json_response, options)
656
657
  return json_response['success'] ? 0 : 1
657
658
  else
658
- target_desc = ""
659
+ target_desc = nil
659
660
  if instances.size() > 0
660
661
  target_desc = (instances.size() == 1) ? "instance #{instances[0]['name']}" : "#{instances.size()} instances"
661
662
  elsif servers.size() > 0
662
663
  target_desc = (servers.size() == 1) ? "host #{servers[0]['name']}" : "#{servers.size()} hosts"
663
- elsif target_type == 'appliance'
664
- target_desc = "appliance"
665
664
  end
666
- print_green_success "Executing workflow #{workflow['name']} on #{target_desc}"
665
+ if target_desc
666
+ print_green_success "Executing workflow #{workflow['name']} on #{target_desc}"
667
+ else
668
+ print_green_success "Executing workflow #{workflow['name']}"
669
+ end
667
670
  # todo: refresh, use get processId and load process record isntead? err
668
671
  if json_response["jobExecution"] && json_response["jobExecution"]["id"]
669
672
  get_args = [json_response["jobExecution"]["id"], "--details"] + (options[:remote] ? ["-r",options[:remote]] : [])