morpheus-cli 4.1.8 → 4.1.9

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 (72) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +1 -1
  3. data/lib/morpheus/api/api_client.rb +24 -0
  4. data/lib/morpheus/api/{old_cypher_interface.rb → budgets_interface.rb} +10 -11
  5. data/lib/morpheus/api/cloud_datastores_interface.rb +7 -0
  6. data/lib/morpheus/api/cloud_resource_pools_interface.rb +2 -2
  7. data/lib/morpheus/api/cypher_interface.rb +18 -12
  8. data/lib/morpheus/api/health_interface.rb +72 -0
  9. data/lib/morpheus/api/instances_interface.rb +1 -1
  10. data/lib/morpheus/api/library_instance_types_interface.rb +7 -0
  11. data/lib/morpheus/api/log_settings_interface.rb +6 -0
  12. data/lib/morpheus/api/network_security_servers_interface.rb +30 -0
  13. data/lib/morpheus/api/price_sets_interface.rb +42 -0
  14. data/lib/morpheus/api/prices_interface.rb +68 -0
  15. data/lib/morpheus/api/provisioning_settings_interface.rb +29 -0
  16. data/lib/morpheus/api/servers_interface.rb +1 -1
  17. data/lib/morpheus/api/service_plans_interface.rb +34 -11
  18. data/lib/morpheus/api/task_sets_interface.rb +8 -0
  19. data/lib/morpheus/api/tasks_interface.rb +8 -0
  20. data/lib/morpheus/cli.rb +6 -3
  21. data/lib/morpheus/cli/appliance_settings_command.rb +13 -5
  22. data/lib/morpheus/cli/approvals_command.rb +1 -1
  23. data/lib/morpheus/cli/apps.rb +88 -28
  24. data/lib/morpheus/cli/backup_settings_command.rb +1 -1
  25. data/lib/morpheus/cli/blueprints_command.rb +2 -0
  26. data/lib/morpheus/cli/budgets_command.rb +672 -0
  27. data/lib/morpheus/cli/cli_command.rb +13 -2
  28. data/lib/morpheus/cli/cli_registry.rb +1 -0
  29. data/lib/morpheus/cli/clusters.rb +40 -274
  30. data/lib/morpheus/cli/commands/standard/benchmark_command.rb +114 -66
  31. data/lib/morpheus/cli/commands/standard/coloring_command.rb +12 -0
  32. data/lib/morpheus/cli/commands/standard/curl_command.rb +31 -6
  33. data/lib/morpheus/cli/commands/standard/echo_command.rb +8 -3
  34. data/lib/morpheus/cli/commands/standard/set_prompt_command.rb +1 -1
  35. data/lib/morpheus/cli/containers_command.rb +37 -24
  36. data/lib/morpheus/cli/cypher_command.rb +191 -150
  37. data/lib/morpheus/cli/health_command.rb +903 -0
  38. data/lib/morpheus/cli/hosts.rb +43 -32
  39. data/lib/morpheus/cli/instances.rb +119 -68
  40. data/lib/morpheus/cli/jobs_command.rb +1 -1
  41. data/lib/morpheus/cli/library_instance_types_command.rb +61 -11
  42. data/lib/morpheus/cli/library_option_types_command.rb +2 -2
  43. data/lib/morpheus/cli/log_settings_command.rb +46 -3
  44. data/lib/morpheus/cli/logs_command.rb +24 -17
  45. data/lib/morpheus/cli/mixins/accounts_helper.rb +2 -0
  46. data/lib/morpheus/cli/mixins/logs_helper.rb +73 -19
  47. data/lib/morpheus/cli/mixins/print_helper.rb +29 -1
  48. data/lib/morpheus/cli/mixins/provisioning_helper.rb +554 -96
  49. data/lib/morpheus/cli/mixins/whoami_helper.rb +13 -1
  50. data/lib/morpheus/cli/networks_command.rb +3 -0
  51. data/lib/morpheus/cli/option_types.rb +83 -53
  52. data/lib/morpheus/cli/price_sets_command.rb +543 -0
  53. data/lib/morpheus/cli/prices_command.rb +669 -0
  54. data/lib/morpheus/cli/processes_command.rb +0 -2
  55. data/lib/morpheus/cli/provisioning_settings_command.rb +237 -0
  56. data/lib/morpheus/cli/remote.rb +9 -4
  57. data/lib/morpheus/cli/reports_command.rb +10 -4
  58. data/lib/morpheus/cli/roles.rb +93 -38
  59. data/lib/morpheus/cli/security_groups.rb +10 -0
  60. data/lib/morpheus/cli/service_plans_command.rb +736 -0
  61. data/lib/morpheus/cli/tasks.rb +220 -8
  62. data/lib/morpheus/cli/tenants_command.rb +3 -16
  63. data/lib/morpheus/cli/users.rb +2 -25
  64. data/lib/morpheus/cli/version.rb +1 -1
  65. data/lib/morpheus/cli/whitelabel_settings_command.rb +18 -18
  66. data/lib/morpheus/cli/whoami.rb +28 -10
  67. data/lib/morpheus/cli/workflows.rb +488 -36
  68. data/lib/morpheus/formatters.rb +22 -0
  69. data/morpheus-cli.gemspec +1 -0
  70. metadata +28 -5
  71. data/lib/morpheus/cli/accounts.rb +0 -335
  72. data/lib/morpheus/cli/old_cypher_command.rb +0 -412
@@ -26,7 +26,7 @@ class Morpheus::Cli::Whoami
26
26
  # begin
27
27
  @api_client = establish_remote_appliance_connection(opts.merge({:no_prompt => true, :skip_verify_access_token => true}))
28
28
  @groups_interface = @api_client.groups
29
- @active_group_id = Morpheus::Cli::Groups.active_group
29
+ @active_group_id = Morpheus::Cli::Groups.active_groups[@appliance_name]
30
30
  # rescue Morpheus::Cli::CommandError => err
31
31
  # puts_error err
32
32
  # end
@@ -46,6 +46,9 @@ class Morpheus::Cli::Whoami
46
46
  opts.on( '-n', '--name', "Print only your username." ) do
47
47
  username_only = true
48
48
  end
49
+ opts.on('-a','--all', "Display All Details (Feature Access)") do
50
+ options[:include_feature_access] = true
51
+ end
49
52
  opts.on('-f','--feature-access', "Display Feature Access") do
50
53
  options[:include_feature_access] = true
51
54
  end
@@ -59,12 +62,12 @@ class Morpheus::Cli::Whoami
59
62
  # opts.on(nil,'--instance-type-access', "Display Instance Type Access") do
60
63
  # options[:include_instance_type_access] = true
61
64
  # end
62
- # opts.on('-a','--all-access', "Display All Access Lists") do
63
- # options[:include_feature_access] = true
64
- # options[:include_group_access] = true
65
- # options[:include_cloud_access] = true
66
- # options[:include_instance_type_access] = true
67
- # end
65
+ opts.on('-a','--all-access', "Display All Access Lists") do
66
+ options[:include_feature_access] = true
67
+ options[:include_group_access] = true
68
+ options[:include_cloud_access] = true
69
+ options[:include_instance_type_access] = true
70
+ end
68
71
  opts.on('-t','--token-only', "Print your access token only") do
69
72
  access_token_only = true
70
73
  end
@@ -188,10 +191,25 @@ class Morpheus::Cli::Whoami
188
191
  if @user_permissions
189
192
  print_h2 "Feature Permissions", options
190
193
  print cyan
191
- rows = @user_permissions.collect do |code, access|
192
- {code: code, access: get_access_string(access) }
194
+ begin
195
+ rows = []
196
+ if @user_permissions.is_a?(Hash)
197
+ # api used to return map like [code:access]
198
+ rows = @user_permissions.collect do |code, access|
199
+ {permission: code, access: get_access_string(access) }
200
+ end
201
+ else
202
+ # api now returns an array of objects like [[name:"Foo",code:"foo",access:"full"], ...]
203
+ rows = @user_permissions.collect do |it|
204
+ {permission: (it['name'] || it['code']), access: get_access_string(it['access']) }
205
+ end
206
+ end
207
+ # api sort sux right now
208
+ rows = rows.sort {|a,b| a[:permission] <=> b[:permission] }
209
+ print as_pretty_table(rows, [:permission, :access], options)
210
+ rescue => ex
211
+ puts_error "Failed to parse feature permissions: #{ex}"
193
212
  end
194
- print as_pretty_table(rows, [:code, :access], options)
195
213
  else
196
214
  puts yellow,"No permissions found.",reset
197
215
  end
@@ -6,7 +6,7 @@ require 'morpheus/cli/cli_command'
6
6
  class Morpheus::Cli::Workflows
7
7
  include Morpheus::Cli::CliCommand
8
8
 
9
- register_subcommands :list, :get, :add, :update, :remove
9
+ register_subcommands :list, :get, :add, :update, :remove, :execute
10
10
  set_default_subcommand :list
11
11
 
12
12
  # def initialize()
@@ -15,8 +15,11 @@ class Morpheus::Cli::Workflows
15
15
 
16
16
  def connect(opts)
17
17
  @api_client = establish_remote_appliance_connection(opts)
18
- @tasks_interface = Morpheus::APIClient.new(@access_token,nil,nil, @appliance_url).tasks
19
- @task_sets_interface = Morpheus::APIClient.new(@access_token,nil,nil, @appliance_url).task_sets
18
+ @task_sets_interface = @api_client.task_sets
19
+ @tasks_interface = @api_client.tasks
20
+ @option_types_interface = @api_client.option_types
21
+ @instances_interface = @api_client.instances
22
+ @servers_interface = @api_client.servers
20
23
  end
21
24
 
22
25
 
@@ -77,54 +80,185 @@ class Morpheus::Cli::Workflows
77
80
  def add(args)
78
81
  options = {}
79
82
  params = {}
83
+ tasks = nil
80
84
  task_arg_list = nil
85
+ option_types = nil
86
+ option_type_arg_list = nil
87
+ workflow_type = nil # 'provision'
81
88
  optparse = Morpheus::Cli::OptionParser.new do |opts|
82
89
  opts.banner = subcommand_usage("[name] --tasks taskId:phase,taskId2:phase,taskId3:phase")
83
90
  opts.on("--name NAME", String, "Name for workflow") do |val|
84
91
  params['name'] = val
85
92
  end
86
- opts.on("--tasks x,y,z", Array, "List of tasks to run in order, in the format <Task ID>:<Task Phase> Task Phase is optional, the default is 'provision'.") do |list|
93
+ opts.on("--description DESCRIPTION", String, "Description of workflow") do |val|
94
+ params['description'] = val
95
+ end
96
+ opts.on("-t", "--type TYPE", "Type of workflow. i.e. provision or operation. Default is provision.") do |val|
97
+ workflow_type = val.to_s.downcase
98
+ if workflow_type.include?('provision')
99
+ workflow_type = 'provision'
100
+ elsif workflow_type.include?('operation')
101
+ workflow_type = 'operation'
102
+ end
103
+ params['type'] = workflow_type
104
+ end
105
+ opts.on("--operational", "--operational", "Create an operational workflow, alias for --type operational.") do |val|
106
+ workflow_type = 'operation'
107
+ params['type'] = workflow_type
108
+ end
109
+ opts.on("--tasks [x,y,z]", Array, "List of tasks to run in order, in the format <Task ID>:<Task Phase> Task Phase is optional. Default is the same workflow type: 'provision' or 'operation'.") do |list|
87
110
  task_arg_list = []
88
111
  list.each do |it|
89
112
  task_id, task_phase = it.split(":")
90
113
  task_arg_list << {task_id: task_id.to_s.strip, task_phase: task_phase.to_s.strip}
114
+ end if list
115
+ end
116
+ opts.on("--option-types x,y,z", Array, "List of option type name or IDs. For use with operational workflows to add configuration during execution.") do |list|
117
+ option_type_arg_list = []
118
+ list.each do |it|
119
+ option_type_arg_list << {option_type_id: it.to_s.strip}
91
120
  end
92
121
  end
122
+ opts.on('--platform [PLATFORM]', String, "Platform, eg. linux, windows or osx") do |val|
123
+ params['platform'] = val.to_s.empty? ? nil : val.to_s.downcase
124
+ end
125
+ opts.on('--allow-custom-config [on|off]', String, "Allow Custom Config") do |val|
126
+ params['allowCustomConfig'] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == ''
127
+ end
128
+ opts.on('--visibility VISIBILITY', String, "Visibility, eg. private or public") do |val|
129
+ params['visibility'] = val.to_s.downcase
130
+ end
93
131
  build_common_options(opts, options, [:options, :payload, :json, :dry_run, :quiet, :remote])
94
132
  end
95
133
  optparse.parse!(args)
96
-
134
+ if args.count > 1
135
+ raise_command_error "wrong number of arguments, expected 0-1 and got (#{args.count}) #{args.join(' ')}\n#{optparse}"
136
+ end
97
137
  connect(options)
98
138
  begin
139
+ passed_options = options[:options] ? options[:options].reject {|k,v| k.is_a?(Symbol) } : {}
99
140
  payload = nil
100
141
  if options[:payload]
101
142
  payload = options[:payload]
143
+ payload.deep_merge!({'taskSet' => passed_options}) unless passed_options.empty?
102
144
  else
103
- if args[0] && !params['name']
145
+ params.deep_merge!(passed_options) unless passed_options.empty?
146
+ if args[0]
104
147
  params['name'] = args[0]
105
148
  end
106
- if (!params['name'] || task_arg_list.nil?)
107
- puts optparse
108
- return 1
149
+ # if params['name'].to_s.empty?
150
+ # puts_error "#{Morpheus::Terminal.angry_prompt}missing required option: [name]\n#{optparse}"
151
+ # return 1
152
+ # end
153
+ # if task_arg_list.nil?
154
+ # puts_error "#{Morpheus::Terminal.angry_prompt}missing required option: --tasks\n#{optparse}"
155
+ # return 1
156
+ # end
157
+
158
+ # Name
159
+ if params['name'].nil?
160
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'name', 'fieldLabel' => 'Name', 'type' => 'text', 'required' => true, 'description' => 'Name'}], options[:options], @api_client)
161
+ params['name'] = v_prompt['name'] unless v_prompt['name'].to_s.empty?
109
162
  end
110
- tasks = []
111
- if task_arg_list
112
- task_arg_list.each do |task_arg|
113
- found_task = find_task_by_name_or_id(task_arg[:task_id])
114
- return 1 if found_task.nil?
115
- row = {'taskId' => found_task['id']}
116
- if !task_arg[:task_phase].to_s.strip.empty?
117
- row['taskPhase'] = task_arg[:task_phase]
163
+
164
+ # Description
165
+ if params['description'].nil?
166
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'description', 'fieldLabel' => 'Description', 'type' => 'text', 'description' => 'Description'}], options[:options], @api_client)
167
+ params['description'] = v_prompt['description'] unless v_prompt['description'].to_s.empty?
168
+ end
169
+
170
+ # Type
171
+ if workflow_type.nil?
172
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'type', 'fieldLabel' => 'Type', 'type' => 'select', 'selectOptions' => get_available_workflow_types(), 'required' => true, 'description' => 'Workflow Type', 'defaultValue' => workflow_type || 'provision'}], options[:options], @api_client)
173
+ workflow_type = v_prompt['type'] unless v_prompt['type'].to_s.empty?
174
+ params['type'] = workflow_type
175
+ end
176
+
177
+ # Tasks
178
+ while tasks.nil? do
179
+ if task_arg_list.nil?
180
+ tasks_val = nil
181
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'tasks', 'fieldLabel' => 'Tasks', 'type' => 'text', 'required' => false, 'description' => "List of tasks to run in order, in the format <Task ID>:<Task Phase> Task Phase is optional. Default is the same workflow type: 'provision' or 'operation'."}], options[:options], @api_client)
182
+ tasks_val = v_prompt['tasks'] unless v_prompt['tasks'].to_s.empty?
183
+ if tasks_val
184
+ task_arg_list = []
185
+ tasks_val.split(",").each do |it|
186
+ task_id, task_phase = it.split(":")
187
+ task_arg_list << {task_id: task_id.to_s.strip, task_phase: task_phase.to_s.strip}
188
+ end
189
+ else
190
+ # empty array is allowed
191
+ tasks = []
192
+ end
193
+ end
194
+ if task_arg_list
195
+ tasks = []
196
+ task_arg_list.each do |task_arg|
197
+ found_task = find_task_by_name_or_id(task_arg[:task_id])
198
+ #return 1 if found_task.nil?
199
+ if found_task.nil?
200
+ task_arg_list = nil
201
+ tasks = nil
202
+ break
203
+ end
204
+ row = {'taskId' => found_task['id']}
205
+ if !task_arg[:task_phase].to_s.strip.empty?
206
+ row['taskPhase'] = task_arg[:task_phase]
207
+ elsif workflow_type == 'operation'
208
+ row['taskPhase'] = 'operation'
209
+ end
210
+ tasks << row
211
+ end
212
+ else
213
+ if options[:no_prompt]
214
+ # empty array is allowed
215
+ tasks = []
216
+ end
217
+ end
218
+ end
219
+
220
+ # Option Types
221
+ if workflow_type == 'operation'
222
+ while option_types.nil? do
223
+ if option_type_arg_list.nil?
224
+ option_types_val = nil
225
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'optionTypes', 'fieldLabel' => 'Option Types', 'type' => 'text', 'description' => "List of option type name or IDs. For use with operational workflows to add configuration during execution."}], options[:options], @api_client)
226
+ option_types_val = v_prompt['optionTypes'] unless v_prompt['optionTypes'].to_s.empty?
227
+ if option_types_val
228
+ option_type_arg_list = []
229
+ option_types_val.split(",").each do |it|
230
+ option_type_arg_list << {option_type_id: it.to_s.strip}
231
+ end
232
+ else
233
+ option_types = [] # not required, break out
234
+ end
235
+ end
236
+ if option_type_arg_list
237
+ option_types = []
238
+ option_type_arg_list.each do |option_type_arg|
239
+ found_option_type = find_option_type_by_name_or_id(option_type_arg[:option_type_id])
240
+ #return 1 if found_option_type.nil?
241
+ if found_option_type.nil?
242
+ option_type_arg_list = nil
243
+ option_types = nil
244
+ break
245
+ end
246
+ option_types << found_option_type['id']
247
+ end
118
248
  end
119
- tasks << row
120
249
  end
121
250
  end
251
+
122
252
  payload = {'taskSet' => {}}
123
253
  params.deep_merge!(options[:options].reject {|k,v| k.is_a?(Symbol) }) if options[:options]
254
+ # params['type'] = workflow_type
124
255
  payload['taskSet'].deep_merge!(params)
125
- if !tasks.empty?
256
+ if tasks
126
257
  payload['taskSet']['tasks'] = tasks
127
258
  end
259
+ if option_types && option_types.size > 0
260
+ payload['taskSet']['optionTypes'] = option_types
261
+ end
128
262
  end
129
263
  @task_sets_interface.setopts(options)
130
264
  if options[:dry_run]
@@ -136,7 +270,7 @@ class Morpheus::Cli::Workflows
136
270
  print JSON.pretty_generate(json_response)
137
271
  else
138
272
  workflow = json_response['taskSet']
139
- print "\n", cyan, "Workflow #{workflow['name']} created successfully", reset, "\n\n"
273
+ print_green_success "Workflow #{workflow['name']} created"
140
274
  get([workflow['id']])
141
275
  end
142
276
  rescue RestClient::Exception => e
@@ -205,6 +339,10 @@ class Morpheus::Cli::Workflows
205
339
  "ID" => 'id',
206
340
  "Name" => 'name',
207
341
  "Description" => 'description',
342
+ "Type" => lambda {|workflow| format_workflow_type(workflow) },
343
+ "Platform" => lambda {|it| format_platform(it['platform']) },
344
+ "Allow Custom Config" => lambda {|it| format_boolean(it['allowCustomConfig']) },
345
+ "Visibility" => lambda {|it| it['visibility'].to_s.capitalize },
208
346
  "Created" => lambda {|it| format_local_dt(it['dateCreated']) },
209
347
  "Updated" => lambda {|it| format_local_dt(it['lastUpdated']) }
210
348
  }
@@ -228,9 +366,26 @@ class Morpheus::Cli::Workflows
228
366
  {"PHASE" => lambda {|it| it['taskPhase'] } }, # not returned yet?
229
367
  ]
230
368
  print cyan
231
- puts as_pretty_table(tasks, task_set_task_columns)
369
+ print as_pretty_table(tasks, task_set_task_columns)
370
+ end
371
+
372
+ workflow_option_types = workflow['optionTypes']
373
+
374
+ if workflow_option_types && workflow_option_types.size() > 0
375
+ print_h2 "Workflow Option Types"
376
+ columns = [
377
+ {"ID" => lambda {|it| it['id'] } },
378
+ {"NAME" => lambda {|it| it['name'] } },
379
+ {"TYPE" => lambda {|it| it['type'] } },
380
+ {"FIELD NAME" => lambda {|it| it['fieldName'] } },
381
+ {"FIELD LABEL" => lambda {|it| it['fieldLabel'] } },
382
+ {"DEFAULT" => lambda {|it| it['defaultValue'] } },
383
+ {"REQUIRED" => lambda {|it| format_boolean it['required'] } },
384
+ ]
385
+ print as_pretty_table(workflow_option_types, columns)
232
386
  end
233
387
  print reset
388
+ print "\n"
234
389
  end
235
390
  rescue RestClient::Exception => e
236
391
  print_rest_exception(e, options)
@@ -241,25 +396,45 @@ class Morpheus::Cli::Workflows
241
396
  def update(args)
242
397
  options = {}
243
398
  params = {}
399
+ tasks = nil
244
400
  task_arg_list = nil
401
+ option_types = nil
402
+ option_type_arg_list = nil
245
403
  optparse = Morpheus::Cli::OptionParser.new do |opts|
246
404
  opts.banner = subcommand_usage("[name] --tasks taskId:phase,taskId2:phase,taskId3:phase")
247
- opts.on("--tasks x,y,z", Array, "New list of tasks to run in the format <Task ID>:<Phase>. Phase is optional, the default is 'provision'.") do |list|
405
+ opts.on("--name NAME", String, "New name for workflow") do |val|
406
+ params['name'] = val
407
+ end
408
+ opts.on("--description DESCRIPTION", String, "Description of workflow") do |val|
409
+ params['description'] = val
410
+ end
411
+ opts.on("--tasks [x,y,z]", Array, "List of tasks to run in order, in the format <Task ID>:<Task Phase> Task Phase is optional. Default is the same workflow type: 'provision' or 'operation'.") do |list|
248
412
  task_arg_list = []
249
413
  list.each do |it|
250
414
  task_id, task_phase = it.split(":")
251
415
  task_arg_list << {task_id: task_id.to_s.strip, task_phase: task_phase.to_s.strip}
252
- end
416
+ end if list
253
417
  end
254
- opts.on("--name NAME", String, "New name for workflow") do |val|
255
- params['name'] = val
418
+ opts.on("--option-types [x,y,z]", Array, "List of option type name or IDs. For use with operational workflows to add configuration during execution.") do |list|
419
+ option_type_arg_list = []
420
+ list.each do |it|
421
+ option_type_arg_list << {option_type_id: it.to_s.strip}
422
+ end if list
423
+ end
424
+ opts.on('--platform [PLATFORM]', String, "Platform, eg. linux, windows or osx") do |val|
425
+ params['platform'] = val.to_s.empty? ? nil : val.to_s.downcase
426
+ end
427
+ opts.on('--allow-custom-config [on|off]', String, "Allow Custom Config") do |val|
428
+ params['allowCustomConfig'] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == ''
429
+ end
430
+ opts.on('--visibility VISIBILITY', String, "Visibility, eg. private or public") do |val|
431
+ params['visibility'] = val.to_s.downcase
256
432
  end
257
433
  build_common_options(opts, options, [:options, :payload, :json, :dry_run, :quiet, :remote])
258
434
  end
259
435
  optparse.parse!(args)
260
- if args.count < 1
261
- puts optparse
262
- exit 1
436
+ if args.count != 1
437
+ raise_command_error "wrong number of arguments, expected 1 and got (#{args.count}) #{args.join(' ')}\n#{optparse}"
263
438
  end
264
439
  workflow_name = args[0]
265
440
  connect(options)
@@ -270,8 +445,8 @@ class Morpheus::Cli::Workflows
270
445
  if options[:payload]
271
446
  payload = options[:payload]
272
447
  else
273
- tasks = []
274
448
  if task_arg_list
449
+ tasks = []
275
450
  task_arg_list.each do |task_arg|
276
451
  found_task = find_task_by_name_or_id(task_arg[:task_id])
277
452
  return 1 if found_task.nil?
@@ -282,12 +457,23 @@ class Morpheus::Cli::Workflows
282
457
  tasks << row
283
458
  end
284
459
  end
460
+ if option_type_arg_list
461
+ option_types = []
462
+ option_type_arg_list.each do |option_type_arg|
463
+ found_option_type = find_option_type_by_name_or_id(option_type_arg[:option_type_id])
464
+ return 1 if found_option_type.nil?
465
+ option_types << found_option_type['id']
466
+ end
467
+ end
285
468
  payload = {'taskSet' => {}}
286
469
  params.deep_merge!(options[:options].reject {|k,v| k.is_a?(Symbol) }) if options[:options]
287
470
  payload['taskSet'].deep_merge!(params)
288
- if !tasks.empty?
471
+ if tasks
289
472
  payload['taskSet']['tasks'] = tasks
290
473
  end
474
+ if option_types
475
+ payload['taskSet']['optionTypes'] = option_types
476
+ end
291
477
  end
292
478
  @task_sets_interface.setopts(options)
293
479
  if options[:dry_run]
@@ -298,7 +484,7 @@ class Morpheus::Cli::Workflows
298
484
  if options[:json]
299
485
  print JSON.pretty_generate(json_response)
300
486
  elsif !options[:quiet]
301
- print "\n", cyan, "Workflow #{json_response['taskSet']['name']} updated successfully", reset, "\n\n"
487
+ print_green_success "Workflow #{json_response['taskSet']['name']} updated"
302
488
  get([workflow['id']])
303
489
  end
304
490
  rescue RestClient::Exception => e
@@ -335,7 +521,7 @@ class Morpheus::Cli::Workflows
335
521
  if options[:json]
336
522
  print JSON.pretty_generate(json_response)
337
523
  elsif !options[:quiet]
338
- print "\n", cyan, "Workflow #{workflow['name']} removed", reset, "\n\n"
524
+ print_green_success "Workflow #{workflow['name']} removed"
339
525
  end
340
526
  rescue RestClient::Exception => e
341
527
  print_rest_exception(e, options)
@@ -343,9 +529,141 @@ class Morpheus::Cli::Workflows
343
529
  end
344
530
  end
345
531
 
532
+ def execute(args)
533
+ params = {}
534
+ options = {}
535
+ target_type = nil
536
+ instance_ids = []
537
+ instances = []
538
+ server_ids = []
539
+ servers = []
540
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
541
+ opts.banner = subcommand_usage("[workflow] --instance [instance] [options]")
542
+ opts.on('--instance INSTANCE', String, "Instance name or id to execute the workflow on. This option can be passed more than once.") do |val|
543
+ target_type = 'instance'
544
+ instance_ids << val
545
+ end
546
+ opts.on('--instances [LIST]', Array, "Instances, comma separated list of instance names or IDs.") do |list|
547
+ target_type = 'instance'
548
+ instance_ids = list.collect {|it| it.to_s.strip.empty? ? nil : it.to_s.strip }.compact.uniq
549
+ end
550
+ opts.on('--host HOST', String, "Host name or id to execute the workflow on. This option can be passed more than once.") do |val|
551
+ target_type = 'server'
552
+ server_ids << val
553
+ end
554
+ opts.on('--hosts [LIST]', Array, "Hosts, comma separated list of host names or IDs.") do |list|
555
+ target_type = 'server'
556
+ server_ids = list.collect {|it| it.to_s.strip.empty? ? nil : it.to_s.strip }.compact.uniq
557
+ end
558
+ opts.on('--server HOST', String, "alias for --host") do |val|
559
+ target_type = 'server'
560
+ server_ids << val
561
+ end
562
+ opts.on('--servers [LIST]', Array, "alias for --hosts") do |list|
563
+ target_type = 'server'
564
+ server_ids = list.collect {|it| it.to_s.strip.empty? ? nil : it.to_s.strip }.compact.uniq
565
+ end
566
+ opts.add_hidden_option('--server')
567
+ opts.add_hidden_option('--servers')
568
+ opts.on('--config [TEXT]', String, "Custom config") do |val|
569
+ params['customConfig'] = val.to_s
570
+ end
571
+ build_common_options(opts, options, [:payload, :options, :json, :dry_run, :remote])
572
+ end
573
+ optparse.parse!(args)
574
+ if args.count != 1
575
+ raise_command_error "wrong number of arguments, expected 1 and got (#{args.count}) #{args.join(' ')}\n#{optparse}"
576
+ end
577
+ workflow_name = args[0]
578
+ connect(options)
579
+ begin
580
+ workflow = find_workflow_by_name_or_id(workflow_name)
581
+ return 1 if workflow.nil?
582
+
583
+ passed_options = options[:options] ? options[:options].reject {|k,v| k.is_a?(Symbol) } : {}
584
+ payload = nil
585
+ if options[:payload]
586
+ payload = options[:payload]
587
+ payload.deep_merge!({'job' => passed_options}) unless passed_options.empty?
588
+ else
589
+ if instance_ids.size > 0 && server_ids.size > 0
590
+ raise_command_error "Pass --instance or --host, not both.\n#{optparse}"
591
+ elsif instance_ids.size > 0
592
+ instance_ids.each do |instance_id|
593
+ instance = find_instance_by_name_or_id(instance_id)
594
+ return 1 if instance.nil?
595
+ instances << instance
596
+ end
597
+ params['instances'] = instances.collect {|it| it['id'] }
598
+ elsif server_ids.size > 0
599
+ server_ids.each do |server_id|
600
+ server = find_server_by_name_or_id(server_id)
601
+ return 1 if server.nil?
602
+ servers << server
603
+ end
604
+ params['servers'] = servers.collect {|it| it['id'] }
605
+ else
606
+ raise_command_error "missing required option: --instance or --host\n#{optparse}"
607
+ end
608
+
609
+ # todo: prompt to workflow optionTypes for customOptions
610
+ custom_options = nil
611
+ if workflow['optionTypes'] && workflow['optionTypes'].size() > 0
612
+ custom_option_types = workflow['optionTypes'].collect {|it|
613
+ it['fieldContext'] = 'customOptions'
614
+ it
615
+ }
616
+ custom_options = Morpheus::Cli::OptionTypes.prompt(custom_option_types, options[:options], @api_client, {})
617
+ end
618
+
619
+ params['targetType'] = target_type
620
+
621
+ job_payload = {}
622
+ job_payload.deep_merge!(params)
623
+ passed_options.delete('customOptions')
624
+ job_payload.deep_merge!(passed_options) unless passed_options.empty?
625
+ if custom_options
626
+ # job_payload.deep_merge!('config' => custom_options)
627
+ job_payload.deep_merge!(custom_options)
628
+ end
629
+ payload = {'job' => job_payload}
630
+ end
631
+
632
+ @task_sets_interface.setopts(options)
633
+ if options[:dry_run]
634
+ print_dry_run @task_sets_interface.dry.run(workflow['id'], payload)
635
+ return 0
636
+ end
637
+ response = @task_sets_interface.run(workflow['id'], payload)
638
+ if options[:json]
639
+ print JSON.pretty_generate(json_response)
640
+ if !response['success']
641
+ return 1
642
+ end
643
+ else
644
+ target_desc = ""
645
+ if instances.size() > 0
646
+ target_desc = (instances.size() == 1) ? "instance #{instances[0]['name']}" : "#{instances.size()} instances"
647
+ elsif servers.size() > 0
648
+ target_desc = (servers.size() == 1) ? "host #{servers[0]['name']}" : "#{servers.size()} hosts"
649
+ end
650
+ print_green_success "Executing workflow #{workflow['name']} on #{target_desc}"
651
+ # todo: load job/execution
652
+ # get([workflow['id']])
653
+ end
654
+ return 0
655
+ rescue RestClient::Exception => e
656
+ print_rest_exception(e, options)
657
+ return 1
658
+ end
659
+ end
346
660
 
347
661
  private
348
662
 
663
+ def get_available_workflow_types
664
+ [{"name" => "Provisioning", "value" => "provision", "isDefault" => true}, {"name" => "Operational", "value" => "operation"}]
665
+ end
666
+
349
667
  def find_workflow_by_name_or_id(val)
350
668
  if val.to_s =~ /\A\d{1,}\Z/
351
669
  return find_workflow_by_id(val)
@@ -361,6 +679,7 @@ class Morpheus::Cli::Workflows
361
679
  rescue RestClient::Exception => e
362
680
  if e.response && e.response.code == 404
363
681
  print_red_alert "Workflow not found by id #{id}"
682
+ return nil
364
683
  else
365
684
  raise e
366
685
  end
@@ -397,6 +716,7 @@ class Morpheus::Cli::Workflows
397
716
  rescue RestClient::Exception => e
398
717
  if e.response && e.response.code == 404
399
718
  print_red_alert "Task not found by id #{id}"
719
+ return nil
400
720
  else
401
721
  raise e
402
722
  end
@@ -422,16 +742,148 @@ class Morpheus::Cli::Workflows
422
742
  columns = [
423
743
  {"ID" => lambda {|workflow| workflow['id'] } },
424
744
  {"NAME" => lambda {|workflow| workflow['name'] } },
745
+ {"DESCRIPTION" => lambda {|workflow| workflow['description'] } },
746
+ {"TYPE" => lambda {|workflow| format_workflow_type(workflow) } },
425
747
  {"TASKS" => lambda {|workflow|
426
- (workflow['taskSetTasks'] || []).sort { |x,y| x['taskOrder'].to_i <=> y['taskOrder'].to_i }.collect { |taskSetTask|
427
- taskSetTask['task']['name']
428
- }.join(', ')
748
+ # (workflow['taskSetTasks'] || []).sort { |x,y| x['taskOrder'].to_i <=> y['taskOrder'].to_i }.collect { |taskSetTask|
749
+ # taskSetTask['task']['name']
750
+ # }.join(', ')
751
+ (workflow['taskSetTasks'] || []).size.to_s
429
752
  } },
430
- {"DATE CREATED" => lambda {|workflow| format_local_dt(workflow['dateCreated']) } },
753
+ {"CREATED" => lambda {|workflow| format_local_dt(workflow['dateCreated']) } },
431
754
  ]
432
755
  if opts[:include_fields]
433
756
  columns = opts[:include_fields]
434
757
  end
435
758
  print as_pretty_table(workflows, columns, opts)
436
759
  end
760
+
761
+ def format_workflow_type(workflow)
762
+ if workflow['type'] == 'provision'
763
+ "Provisioning"
764
+ elsif workflow['type'] == 'operation'
765
+ "Operational"
766
+ else
767
+ workflow['type']
768
+ end
769
+ end
770
+
771
+ def format_platform(platform)
772
+ if platform.nil?
773
+ "All"
774
+ elsif platform == 'osx'
775
+ "OSX"
776
+ else
777
+ platform.to_s.capitalize
778
+ end
779
+ end
780
+
781
+ def find_option_type_by_name_or_id(val)
782
+ if val.to_s =~ /\A\d{1,}\Z/
783
+ return find_option_type_by_id(val)
784
+ else
785
+ return find_option_type_by_name(val)
786
+ end
787
+ end
788
+
789
+ def find_option_type_by_id(id)
790
+ begin
791
+ json_response = @option_types_interface.get(id.to_i)
792
+ return json_response['optionType']
793
+ rescue RestClient::Exception => e
794
+ if e.response && e.response.code == 404
795
+ print_red_alert "Option Type not found by id #{id}"
796
+ return nil
797
+ else
798
+ raise e
799
+ end
800
+ end
801
+ end
802
+
803
+ def find_option_type_by_name(name)
804
+ json_results = @option_types_interface.list({name: name.to_s})
805
+ if json_results['optionTypes'].empty?
806
+ print_red_alert "Option Type not found by name #{name}"
807
+ return nil
808
+ end
809
+ option_type = json_results['optionTypes'][0]
810
+ return option_type
811
+ end
812
+
813
+ def find_instance_by_name_or_id(val)
814
+ if val.to_s =~ /\A\d{1,}\Z/
815
+ return find_instance_by_id(val)
816
+ else
817
+ return find_instance_by_name(val)
818
+ end
819
+ end
820
+
821
+ def find_instance_by_id(id)
822
+ begin
823
+ json_response = @instances_interface.get(id.to_i)
824
+ return json_response['instance']
825
+ rescue RestClient::Exception => e
826
+ if e.response && e.response.code == 404
827
+ print_red_alert "Instance not found by id #{id}"
828
+ return nil
829
+ else
830
+ raise e
831
+ end
832
+ end
833
+ end
834
+
835
+ def find_instance_by_name(name)
836
+ instances = @instances_interface.list({name: name.to_s})['instances']
837
+ if instances.empty?
838
+ print_red_alert "Instance not found by name #{name}"
839
+ return nil
840
+ elsif instances.size > 1
841
+ print_red_alert "#{instances.size} instances found by name #{name}"
842
+ as_pretty_table(instances, [:id, :name], {color: red})
843
+ print_red_alert "Try using ID instead"
844
+ print reset,"\n"
845
+ return nil
846
+ else
847
+ return instances[0]
848
+ end
849
+ end
850
+
851
+ def find_server_by_name_or_id(val)
852
+ if val.to_s =~ /\A\d{1,}\Z/
853
+ return find_server_by_id(val)
854
+ else
855
+ return find_server_by_name(val)
856
+ end
857
+ end
858
+
859
+ def find_server_by_id(id)
860
+ begin
861
+ json_response = @servers_interface.get(id.to_i)
862
+ return json_response['server']
863
+ rescue RestClient::Exception => e
864
+ if e.response && e.response.code == 404
865
+ print_red_alert "Server not found by id #{id}"
866
+ return nil
867
+ else
868
+ raise e
869
+ end
870
+ end
871
+ end
872
+
873
+ def find_server_by_name(name)
874
+ servers = @servers_interface.list({name: name.to_s})['servers']
875
+ if servers.empty?
876
+ print_red_alert "Host not found by name #{name}"
877
+ return nil
878
+ elsif servers.size > 1
879
+ print_red_alert "#{servers.size} hosts found by name #{name}"
880
+ as_pretty_table(servers, [:id, :name], {color: red})
881
+ print_red_alert "Try using ID instead"
882
+ print reset,"\n"
883
+ return nil
884
+ else
885
+ return servers[0]
886
+ end
887
+ end
888
+
437
889
  end