morpheus-cli 4.2.21 → 5.2.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.
- checksums.yaml +4 -4
- data/Dockerfile +1 -1
- data/lib/morpheus/api/api_client.rb +30 -0
- data/lib/morpheus/api/billing_interface.rb +34 -0
- data/lib/morpheus/api/catalog_item_types_interface.rb +9 -0
- data/lib/morpheus/api/deploy_interface.rb +1 -1
- data/lib/morpheus/api/deployments_interface.rb +20 -1
- data/lib/morpheus/api/forgot_password_interface.rb +17 -0
- data/lib/morpheus/api/instances_interface.rb +16 -2
- data/lib/morpheus/api/rest_interface.rb +0 -6
- data/lib/morpheus/api/roles_interface.rb +14 -0
- data/lib/morpheus/api/search_interface.rb +13 -0
- data/lib/morpheus/api/servers_interface.rb +14 -0
- data/lib/morpheus/api/service_catalog_interface.rb +89 -0
- data/lib/morpheus/api/usage_interface.rb +18 -0
- data/lib/morpheus/cli.rb +7 -3
- data/lib/morpheus/cli/apps.rb +6 -27
- data/lib/morpheus/cli/backup_jobs_command.rb +3 -0
- data/lib/morpheus/cli/backups_command.rb +3 -0
- data/lib/morpheus/cli/catalog_item_types_command.rb +622 -0
- data/lib/morpheus/cli/cli_command.rb +70 -21
- data/lib/morpheus/cli/commands/standard/curl_command.rb +26 -12
- data/lib/morpheus/cli/commands/standard/history_command.rb +3 -1
- data/lib/morpheus/cli/commands/standard/man_command.rb +74 -40
- data/lib/morpheus/cli/commands/standard/source_command.rb +1 -1
- data/lib/morpheus/cli/commands/standard/update_command.rb +76 -0
- data/lib/morpheus/cli/containers_command.rb +14 -24
- data/lib/morpheus/cli/cypher_command.rb +6 -2
- data/lib/morpheus/cli/deploy.rb +199 -90
- data/lib/morpheus/cli/deployments.rb +341 -28
- data/lib/morpheus/cli/deploys.rb +206 -41
- data/lib/morpheus/cli/error_handler.rb +7 -0
- data/lib/morpheus/cli/forgot_password.rb +133 -0
- data/lib/morpheus/cli/groups.rb +1 -1
- data/lib/morpheus/cli/health_command.rb +59 -2
- data/lib/morpheus/cli/hosts.rb +265 -34
- data/lib/morpheus/cli/instances.rb +186 -100
- data/lib/morpheus/cli/invoices_command.rb +33 -16
- data/lib/morpheus/cli/jobs_command.rb +28 -6
- data/lib/morpheus/cli/library_option_lists_command.rb +15 -7
- data/lib/morpheus/cli/library_option_types_command.rb +5 -2
- data/lib/morpheus/cli/logs_command.rb +9 -6
- data/lib/morpheus/cli/mixins/accounts_helper.rb +12 -7
- data/lib/morpheus/cli/mixins/backups_helper.rb +2 -4
- data/lib/morpheus/cli/mixins/deployments_helper.rb +31 -3
- data/lib/morpheus/cli/mixins/option_source_helper.rb +1 -1
- data/lib/morpheus/cli/mixins/print_helper.rb +46 -21
- data/lib/morpheus/cli/mixins/provisioning_helper.rb +100 -4
- data/lib/morpheus/cli/network_pools_command.rb +14 -6
- data/lib/morpheus/cli/option_types.rb +271 -22
- data/lib/morpheus/cli/ping.rb +0 -1
- data/lib/morpheus/cli/remote.rb +35 -12
- data/lib/morpheus/cli/reports_command.rb +99 -30
- data/lib/morpheus/cli/roles.rb +453 -113
- data/lib/morpheus/cli/search_command.rb +182 -0
- data/lib/morpheus/cli/service_catalog_command.rb +1474 -0
- data/lib/morpheus/cli/service_plans_command.rb +2 -2
- data/lib/morpheus/cli/setup.rb +1 -1
- data/lib/morpheus/cli/shell.rb +33 -11
- data/lib/morpheus/cli/storage_providers_command.rb +40 -56
- data/lib/morpheus/cli/tasks.rb +29 -32
- data/lib/morpheus/cli/usage_command.rb +203 -0
- data/lib/morpheus/cli/user_settings_command.rb +1 -0
- data/lib/morpheus/cli/users.rb +12 -1
- data/lib/morpheus/cli/version.rb +1 -1
- data/lib/morpheus/cli/virtual_images.rb +429 -254
- data/lib/morpheus/cli/whoami.rb +6 -6
- data/lib/morpheus/cli/workflows.rb +34 -41
- data/lib/morpheus/formatters.rb +75 -7
- data/lib/morpheus/terminal.rb +6 -2
- metadata +14 -2
@@ -225,6 +225,9 @@ EOT
|
|
225
225
|
print_dry_run @backup_jobs_interface.dry.destroy(backup_job['id'], params)
|
226
226
|
return
|
227
227
|
end
|
228
|
+
unless options[:yes] || Morpheus::Cli::OptionTypes.confirm("Are you sure you want to delete the backup #{backup['name']}?")
|
229
|
+
return 9, "aborted command"
|
230
|
+
end
|
228
231
|
json_response = @backup_jobs_interface.destroy(backup_job['id'], params)
|
229
232
|
render_response(json_response, options) do
|
230
233
|
print_green_success "Removed backup job #{backup_job['name']}"
|
@@ -215,6 +215,9 @@ EOT
|
|
215
215
|
print_dry_run @backups_interface.dry.destroy(backup['id'], params)
|
216
216
|
return
|
217
217
|
end
|
218
|
+
unless options[:yes] || Morpheus::Cli::OptionTypes.confirm("Are you sure you want to delete the backup #{backup['name']}?")
|
219
|
+
return 9, "aborted command"
|
220
|
+
end
|
218
221
|
json_response = @backups_interface.destroy(backup['id'], params)
|
219
222
|
render_response(json_response, options) do
|
220
223
|
print_green_success "Removed backup #{backup['name']}"
|
@@ -0,0 +1,622 @@
|
|
1
|
+
require 'morpheus/cli/cli_command'
|
2
|
+
|
3
|
+
# CLI command self service
|
4
|
+
# UI is Tools: Self Service - Catalog
|
5
|
+
# API is /catalog-item-types and returns catalogItemTypes
|
6
|
+
class Morpheus::Cli::CatalogItemTypesCommand
|
7
|
+
include Morpheus::Cli::CliCommand
|
8
|
+
include Morpheus::Cli::LibraryHelper
|
9
|
+
include Morpheus::Cli::OptionSourceHelper
|
10
|
+
|
11
|
+
# set_command_name :'catalog-types'
|
12
|
+
set_command_name :'self-service'
|
13
|
+
set_command_description "Self Service: View and manage catalog item types"
|
14
|
+
|
15
|
+
register_subcommands :list, :get, :add, :update, :remove
|
16
|
+
|
17
|
+
def connect(opts)
|
18
|
+
@api_client = establish_remote_appliance_connection(opts)
|
19
|
+
@catalog_item_types_interface = @api_client.catalog_item_types
|
20
|
+
@option_types_interface = @api_client.option_types
|
21
|
+
end
|
22
|
+
|
23
|
+
def handle(args)
|
24
|
+
handle_subcommand(args)
|
25
|
+
end
|
26
|
+
|
27
|
+
def list(args)
|
28
|
+
options = {}
|
29
|
+
params = {}
|
30
|
+
ref_ids = []
|
31
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
32
|
+
opts.banner = subcommand_usage("[search]")
|
33
|
+
opts.on( '--enabled [on|off]', String, "Filter by enabled" ) do |val|
|
34
|
+
params['enabled'] = (val.to_s != 'false' && val.to_s != 'off')
|
35
|
+
end
|
36
|
+
opts.on( '--featured [on|off]', String, "Filter by featured" ) do |val|
|
37
|
+
params['featured'] = (val.to_s != 'false' && val.to_s != 'off')
|
38
|
+
end
|
39
|
+
build_standard_list_options(opts, options)
|
40
|
+
opts.footer = "List catalog item types."
|
41
|
+
end
|
42
|
+
optparse.parse!(args)
|
43
|
+
connect(options)
|
44
|
+
# verify_args!(args:args, optparse:optparse, count:0)
|
45
|
+
if args.count > 0
|
46
|
+
options[:phrase] = args.join(" ")
|
47
|
+
end
|
48
|
+
params.merge!(parse_list_options(options))
|
49
|
+
@catalog_item_types_interface.setopts(options)
|
50
|
+
if options[:dry_run]
|
51
|
+
print_dry_run @catalog_item_types_interface.dry.list(params)
|
52
|
+
return
|
53
|
+
end
|
54
|
+
json_response = @catalog_item_types_interface.list(params)
|
55
|
+
catalog_item_types = json_response[catalog_item_type_list_key]
|
56
|
+
render_response(json_response, options, catalog_item_type_list_key) do
|
57
|
+
print_h1 "Morpheus Catalog Item Types", parse_list_subtitles(options), options
|
58
|
+
if catalog_item_types.empty?
|
59
|
+
print cyan,"No catalog item types found.",reset,"\n"
|
60
|
+
else
|
61
|
+
list_columns = catalog_item_type_column_definitions.upcase_keys!
|
62
|
+
list_columns.delete("Blueprint")
|
63
|
+
list_columns.delete("Workflow")
|
64
|
+
list_columns.delete("Context")
|
65
|
+
#list_columns["Config"] = lambda {|it| truncate_string(it['config'], 100) }
|
66
|
+
print as_pretty_table(catalog_item_types, list_columns.upcase_keys!, options)
|
67
|
+
print_results_pagination(json_response)
|
68
|
+
end
|
69
|
+
print reset,"\n"
|
70
|
+
end
|
71
|
+
if catalog_item_types.empty?
|
72
|
+
return 1, "no catalog item types found"
|
73
|
+
else
|
74
|
+
return 0, nil
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def get(args)
|
79
|
+
params = {}
|
80
|
+
options = {}
|
81
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
82
|
+
opts.banner = subcommand_usage("[type]")
|
83
|
+
opts.on( '-c', '--config', "Display raw config only. Default is YAML. Combine with -j for JSON instead." ) do
|
84
|
+
options[:show_config] = true
|
85
|
+
end
|
86
|
+
# opts.on('--no-config', "Do not display Config YAML." ) do
|
87
|
+
# options[:no_config] = true
|
88
|
+
# end
|
89
|
+
opts.on('--no-content', "Do not display Content." ) do
|
90
|
+
options[:no_content] = true
|
91
|
+
end
|
92
|
+
build_standard_get_options(opts, options)
|
93
|
+
opts.footer = <<-EOT
|
94
|
+
Get details about a specific catalog item type.
|
95
|
+
[type] is required. This is the name or id of a catalog item type.
|
96
|
+
EOT
|
97
|
+
end
|
98
|
+
optparse.parse!(args)
|
99
|
+
verify_args!(args:args, optparse:optparse, min:1)
|
100
|
+
connect(options)
|
101
|
+
id_list = parse_id_list(args)
|
102
|
+
return run_command_for_each_arg(id_list) do |arg|
|
103
|
+
_get(arg, params, options)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def _get(id, params, options)
|
108
|
+
catalog_item_type = nil
|
109
|
+
if id.to_s !~ /\A\d{1,}\Z/
|
110
|
+
catalog_item_type = find_catalog_item_type_by_name(id)
|
111
|
+
return 1, "catalog item type not found for #{id}" if catalog_item_type.nil?
|
112
|
+
id = catalog_item_type['id']
|
113
|
+
end
|
114
|
+
@catalog_item_types_interface.setopts(options)
|
115
|
+
if options[:dry_run]
|
116
|
+
print_dry_run @catalog_item_types_interface.dry.get(id, params)
|
117
|
+
return
|
118
|
+
end
|
119
|
+
# skip extra query, list has same data as show right now
|
120
|
+
if catalog_item_type
|
121
|
+
json_response = {catalog_item_type_object_key => catalog_item_type}
|
122
|
+
else
|
123
|
+
json_response = @catalog_item_types_interface.get(id, params)
|
124
|
+
end
|
125
|
+
catalog_item_type = json_response[catalog_item_type_object_key]
|
126
|
+
config = catalog_item_type['config'] || {}
|
127
|
+
# export just the config as json or yaml (default)
|
128
|
+
if options[:show_config]
|
129
|
+
unless options[:json] || options[:yaml] || options[:csv]
|
130
|
+
options[:yaml] = true
|
131
|
+
end
|
132
|
+
return render_with_format(config, options)
|
133
|
+
end
|
134
|
+
render_response(json_response, options, catalog_item_type_object_key) do
|
135
|
+
print_h1 "Catalog Item Type Details", [], options
|
136
|
+
print cyan
|
137
|
+
show_columns = catalog_item_type_column_definitions
|
138
|
+
show_columns.delete("Blueprint") unless catalog_item_type['blueprint']
|
139
|
+
show_columns.delete("Workflow") unless catalog_item_type['workflow']
|
140
|
+
show_columns.delete("Context") unless catalog_item_type['context'] # workflow context
|
141
|
+
print_description_list(show_columns, catalog_item_type)
|
142
|
+
|
143
|
+
if catalog_item_type['optionTypes'] && catalog_item_type['optionTypes'].size > 0
|
144
|
+
print_h2 "Option Types"
|
145
|
+
opt_columns = [
|
146
|
+
{"ID" => lambda {|it| it['id'] } },
|
147
|
+
{"NAME" => lambda {|it| it['name'] } },
|
148
|
+
{"TYPE" => lambda {|it| it['type'] } },
|
149
|
+
{"FIELD NAME" => lambda {|it| it['fieldName'] } },
|
150
|
+
{"FIELD LABEL" => lambda {|it| it['fieldLabel'] } },
|
151
|
+
{"DEFAULT" => lambda {|it| it['defaultValue'] } },
|
152
|
+
{"REQUIRED" => lambda {|it| format_boolean it['required'] } },
|
153
|
+
]
|
154
|
+
print as_pretty_table(catalog_item_type['optionTypes'], opt_columns)
|
155
|
+
else
|
156
|
+
# print cyan,"No option types found for this catalog item.","\n",reset
|
157
|
+
end
|
158
|
+
|
159
|
+
item_type_code = catalog_item_type['type'].to_s.downcase
|
160
|
+
if options[:no_config] != true
|
161
|
+
if item_type_code == 'instance'
|
162
|
+
print_h2 "Config YAML"
|
163
|
+
if config
|
164
|
+
#print reset,(JSON.pretty_generate(config) rescue config),"\n",reset
|
165
|
+
#print reset,(as_yaml(config, options) rescue config),"\n",reset
|
166
|
+
config_string = as_yaml(config, options) rescue config
|
167
|
+
config_lines = config_string.split("\n")
|
168
|
+
config_line_count = config_lines.size
|
169
|
+
max_lines = 10
|
170
|
+
if config_lines.size > max_lines
|
171
|
+
config_string = config_lines.first(max_lines).join("\n")
|
172
|
+
config_string << "\n\n"
|
173
|
+
config_string << "#{dark}(#{(config_line_count - max_lines)} more lines were not shown, use -c to show the config)#{reset}"
|
174
|
+
#config_string << "\n"
|
175
|
+
end
|
176
|
+
# strip --- yaml header
|
177
|
+
if config_string[0..3] == "---\n"
|
178
|
+
config_string = config_string[4..-1]
|
179
|
+
end
|
180
|
+
print reset,config_string.chomp("\n"),"\n",reset
|
181
|
+
else
|
182
|
+
print reset,"(blank)","\n",reset
|
183
|
+
end
|
184
|
+
elsif item_type_code == 'blueprint' || item_type_code == 'apptemplate' || item_type_code == 'app'
|
185
|
+
print_h2 "App Spec"
|
186
|
+
if catalog_item_type['appSpec']
|
187
|
+
#print reset,(JSON.pretty_generate(config) rescue config),"\n",reset
|
188
|
+
#print reset,(as_yaml(config, options) rescue config),"\n",reset
|
189
|
+
config_string = catalog_item_type['appSpec'] || ""
|
190
|
+
config_lines = config_string.split("\n")
|
191
|
+
config_line_count = config_lines.size
|
192
|
+
max_lines = 10
|
193
|
+
if config_lines.size > max_lines
|
194
|
+
config_string = config_lines.first(max_lines).join("\n")
|
195
|
+
config_string << "\n\n"
|
196
|
+
config_string << "#{dark}(#{(config_line_count - max_lines)} more lines were not shown, use -c to show the config)#{reset}"
|
197
|
+
#config_string << "\n"
|
198
|
+
end
|
199
|
+
# strip --- yaml header
|
200
|
+
if config_string[0..3] == "---\n"
|
201
|
+
config_string = config_string[4..-1]
|
202
|
+
end
|
203
|
+
print reset,config_string.chomp("\n"),"\n",reset
|
204
|
+
else
|
205
|
+
print reset,"(blank)","\n",reset
|
206
|
+
end
|
207
|
+
elsif item_type_code == 'workflow' || item_type_code == 'operationalworkflow' || item_type_code == 'taskset'
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
# Content (Wiki Page)
|
212
|
+
if !catalog_item_type["content"].to_s.empty? && options[:no_content] != true
|
213
|
+
print_h2 "Content"
|
214
|
+
print reset,catalog_item_type["content"].chomp("\n"),"\n",reset
|
215
|
+
end
|
216
|
+
|
217
|
+
print reset,"\n"
|
218
|
+
end
|
219
|
+
return 0, nil
|
220
|
+
end
|
221
|
+
|
222
|
+
def add(args)
|
223
|
+
options = {}
|
224
|
+
params = {}
|
225
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
226
|
+
opts.banner = subcommand_usage("[name] [options]")
|
227
|
+
# opts.on('-t', '--type [instance|blueprint|workflow]', "Item Type, default is instance.") do |val|
|
228
|
+
# # params['type'] = val.to_s.downcase
|
229
|
+
# options[:options]['type'] = val.to_s.downcase
|
230
|
+
# end
|
231
|
+
build_option_type_options(opts, options, add_catalog_item_type_option_types)
|
232
|
+
opts.on('--config-file FILE', String, "Config from a local JSON or YAML file") do |val|
|
233
|
+
options[:config_file] = val.to_s
|
234
|
+
file_content = nil
|
235
|
+
full_filename = File.expand_path(options[:config_file])
|
236
|
+
if File.exists?(full_filename)
|
237
|
+
file_content = File.read(full_filename)
|
238
|
+
else
|
239
|
+
print_red_alert "File not found: #{full_filename}"
|
240
|
+
return 1
|
241
|
+
end
|
242
|
+
parse_result = parse_json_or_yaml(file_content)
|
243
|
+
config_map = parse_result[:data]
|
244
|
+
if config_map.nil?
|
245
|
+
# todo: bubble up JSON.parse error message
|
246
|
+
raise_command_error "Failed to parse config as YAML or JSON. Error: #{parse_result[:err]}"
|
247
|
+
#raise_command_error "Failed to parse config as valid YAML or JSON."
|
248
|
+
else
|
249
|
+
params['config'] = config_map
|
250
|
+
options[:options]['config'] = params['config'] # or file_content
|
251
|
+
end
|
252
|
+
end
|
253
|
+
opts.on('--option-types [x,y,z]', Array, "List of Option Type IDs") do |list|
|
254
|
+
if list.nil?
|
255
|
+
params['optionTypes'] = []
|
256
|
+
else
|
257
|
+
params['optionTypes'] = list.collect {|it| it.to_s.strip.empty? ? nil : it.to_s.strip }.compact.uniq
|
258
|
+
end
|
259
|
+
end
|
260
|
+
opts.on('--optionTypes [x,y,z]', Array, "List of Option Type IDs") do |list|
|
261
|
+
if list.nil?
|
262
|
+
params['optionTypes'] = []
|
263
|
+
else
|
264
|
+
params['optionTypes'] = list.collect {|it| it.to_s.strip.empty? ? nil : it.to_s.strip }.compact.uniq
|
265
|
+
end
|
266
|
+
end
|
267
|
+
opts.add_hidden_option('--optionTypes')
|
268
|
+
build_option_type_options(opts, options, add_catalog_item_type_advanced_option_types)
|
269
|
+
build_standard_add_options(opts, options)
|
270
|
+
opts.footer = <<-EOT
|
271
|
+
Create a new catalog item type.
|
272
|
+
EOT
|
273
|
+
end
|
274
|
+
optparse.parse!(args)
|
275
|
+
verify_args!(args:args, optparse:optparse, min:0, max:1)
|
276
|
+
options[:options]['name'] = args[0] if args[0]
|
277
|
+
connect(options)
|
278
|
+
payload = {}
|
279
|
+
if options[:payload]
|
280
|
+
payload = options[:payload]
|
281
|
+
payload.deep_merge!({catalog_item_type_object_key => parse_passed_options(options)})
|
282
|
+
else
|
283
|
+
payload.deep_merge!({catalog_item_type_object_key => parse_passed_options(options)})
|
284
|
+
# Type prompt first
|
285
|
+
#params['type'] = Morpheus::Cli::OptionTypes.no_prompt([{'fieldName' => 'type', 'fieldLabel' => 'Type', 'type' => 'select', 'selectOptions' => [{'name' => 'Instance', 'value' => 'instance'}, {'name' => 'Blueprint', 'value' => 'blueprint'}, {'name' => 'Workflow', 'value' => 'workflow'}], 'defaultValue' => 'instance', 'required' => true}], options[:options], @api_client, options[:params])['type']
|
286
|
+
v_prompt = Morpheus::Cli::OptionTypes.prompt(add_catalog_item_type_option_types(), options[:options], @api_client, options[:params])
|
287
|
+
params.deep_merge!(v_prompt)
|
288
|
+
advanced_config = Morpheus::Cli::OptionTypes.no_prompt(add_catalog_item_type_advanced_option_types, options[:options], @api_client, options[:params])
|
289
|
+
advanced_config.deep_compact!
|
290
|
+
params.deep_merge!(advanced_config)
|
291
|
+
# convert checkbox "on" and "off" to true and false
|
292
|
+
params.booleanize!
|
293
|
+
# convert type to refType until api accepts type
|
294
|
+
# if params['type'] && !params['refType']
|
295
|
+
# if params['type'].to_s.downcase == 'instance'
|
296
|
+
# params['refType'] = 'InstanceType'
|
297
|
+
# elsif params['type'].to_s.downcase == 'blueprint'
|
298
|
+
# params['refType'] = 'AppTemplate'
|
299
|
+
# elsif params['type'].to_s.downcase == 'workflow'
|
300
|
+
# params['refType'] = 'OperationalWorkflow'
|
301
|
+
# end
|
302
|
+
# end
|
303
|
+
# convert config string to a map
|
304
|
+
config = params['config']
|
305
|
+
if config && config.is_a?(String)
|
306
|
+
parse_result = parse_json_or_yaml(config)
|
307
|
+
config_map = parse_result[:data]
|
308
|
+
if config_map.nil?
|
309
|
+
# todo: bubble up JSON.parse error message
|
310
|
+
raise_command_error "Failed to parse config as YAML or JSON. Error: #{parse_result[:err]}"
|
311
|
+
#raise_command_error "Failed to parse config as valid YAML or JSON."
|
312
|
+
else
|
313
|
+
params['config'] = config_map
|
314
|
+
end
|
315
|
+
end
|
316
|
+
# massage association params a bit
|
317
|
+
params['workflow'] = {'id' => params['workflow']} if params['workflow'] && !params['workflow'].is_a?(Hash)
|
318
|
+
params['blueprint'] = {'id' => params['blueprint']} if params['blueprint'] && !params['blueprint'].is_a?(Hash)
|
319
|
+
prompt_results = prompt_for_option_types(params, options, @api_client)
|
320
|
+
if prompt_results[:success]
|
321
|
+
params['optionTypes'] = prompt_results[:data] unless prompt_results[:data].nil?
|
322
|
+
else
|
323
|
+
return 1, "failed to parse optionTypes"
|
324
|
+
end
|
325
|
+
payload[catalog_item_type_object_key].deep_merge!(params)
|
326
|
+
end
|
327
|
+
@catalog_item_types_interface.setopts(options)
|
328
|
+
if options[:dry_run]
|
329
|
+
print_dry_run @catalog_item_types_interface.dry.create(payload)
|
330
|
+
return 0, nil
|
331
|
+
end
|
332
|
+
json_response = @catalog_item_types_interface.create(payload)
|
333
|
+
catalog_item_type = json_response[catalog_item_type_object_key]
|
334
|
+
render_response(json_response, options, catalog_item_type_object_key) do
|
335
|
+
print_green_success "Added catalog item type #{catalog_item_type['name']}"
|
336
|
+
return _get(catalog_item_type["id"], {}, options)
|
337
|
+
end
|
338
|
+
return 0, nil
|
339
|
+
end
|
340
|
+
|
341
|
+
def update(args)
|
342
|
+
options = {}
|
343
|
+
params = {}
|
344
|
+
payload = {}
|
345
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
346
|
+
opts.banner = subcommand_usage("[type] [options]")
|
347
|
+
build_option_type_options(opts, options, update_catalog_item_type_option_types)
|
348
|
+
opts.on('--config-file FILE', String, "Config from a local JSON or YAML file") do |val|
|
349
|
+
options[:config_file] = val.to_s
|
350
|
+
file_content = nil
|
351
|
+
full_filename = File.expand_path(options[:config_file])
|
352
|
+
if File.exists?(full_filename)
|
353
|
+
file_content = File.read(full_filename)
|
354
|
+
else
|
355
|
+
print_red_alert "File not found: #{full_filename}"
|
356
|
+
return 1
|
357
|
+
end
|
358
|
+
parse_result = parse_json_or_yaml(file_content)
|
359
|
+
config_map = parse_result[:data]
|
360
|
+
if config_map.nil?
|
361
|
+
# todo: bubble up JSON.parse error message
|
362
|
+
raise_command_error "Failed to parse config as YAML or JSON. Error: #{parse_result[:err]}"
|
363
|
+
#raise_command_error "Failed to parse config as valid YAML or JSON."
|
364
|
+
else
|
365
|
+
params['config'] = config_map
|
366
|
+
options[:options]['config'] = params['config'] # or file_content
|
367
|
+
end
|
368
|
+
end
|
369
|
+
opts.on('--option-types [x,y,z]', Array, "List of Option Type IDs") do |list|
|
370
|
+
if list.nil?
|
371
|
+
params['optionTypes'] = []
|
372
|
+
else
|
373
|
+
params['optionTypes'] = list.collect {|it| it.to_s.strip.empty? ? nil : it.to_s.strip }.compact.uniq
|
374
|
+
end
|
375
|
+
end
|
376
|
+
opts.on('--optionTypes [x,y,z]', Array, "List of Option Type IDs") do |list|
|
377
|
+
if list.nil?
|
378
|
+
params['optionTypes'] = []
|
379
|
+
else
|
380
|
+
params['optionTypes'] = list.collect {|it| it.to_s.strip.empty? ? nil : it.to_s.strip }.compact.uniq
|
381
|
+
end
|
382
|
+
end
|
383
|
+
opts.add_hidden_option('--optionTypes')
|
384
|
+
build_option_type_options(opts, options, update_catalog_item_type_advanced_option_types)
|
385
|
+
build_standard_update_options(opts, options)
|
386
|
+
opts.footer = <<-EOT
|
387
|
+
Update a catalog item type.
|
388
|
+
[type] is required. This is the name or id of a catalog item type.
|
389
|
+
EOT
|
390
|
+
end
|
391
|
+
optparse.parse!(args)
|
392
|
+
verify_args!(args:args, optparse:optparse, count:1)
|
393
|
+
connect(options)
|
394
|
+
catalog_item_type = find_catalog_item_type_by_name_or_id(args[0])
|
395
|
+
return 1 if catalog_item_type.nil?
|
396
|
+
payload = {}
|
397
|
+
if options[:payload]
|
398
|
+
payload = options[:payload]
|
399
|
+
payload.deep_merge!({catalog_item_type_object_key => parse_passed_options(options)})
|
400
|
+
else
|
401
|
+
payload.deep_merge!({catalog_item_type_object_key => parse_passed_options(options)})
|
402
|
+
# do not prompt on update
|
403
|
+
v_prompt = Morpheus::Cli::OptionTypes.no_prompt(update_catalog_item_type_option_types, options[:options], @api_client, options[:params])
|
404
|
+
v_prompt.deep_compact!
|
405
|
+
params.deep_merge!(v_prompt)
|
406
|
+
advanced_config = Morpheus::Cli::OptionTypes.no_prompt(update_catalog_item_type_advanced_option_types, options[:options], @api_client, options[:params])
|
407
|
+
advanced_config.deep_compact!
|
408
|
+
params.deep_merge!(advanced_config)
|
409
|
+
# convert checkbox "on" and "off" to true and false
|
410
|
+
params.booleanize!
|
411
|
+
|
412
|
+
# convert config string to a map
|
413
|
+
config = params['config']
|
414
|
+
if config && config.is_a?(String)
|
415
|
+
parse_result = parse_json_or_yaml(config)
|
416
|
+
config_map = parse_result[:data]
|
417
|
+
if config_map.nil?
|
418
|
+
# todo: bubble up JSON.parse error message
|
419
|
+
raise_command_error "Failed to parse config as YAML or JSON. Error: #{parse_result[:err]}"
|
420
|
+
#raise_command_error "Failed to parse config as valid YAML or JSON."
|
421
|
+
else
|
422
|
+
params['config'] = config_map
|
423
|
+
end
|
424
|
+
end
|
425
|
+
if params['optionTypes']
|
426
|
+
# todo: move to optionSource, so it will be /api/options/optionTypes lol
|
427
|
+
prompt_results = prompt_for_option_types(params, options, @api_client)
|
428
|
+
if prompt_results[:success]
|
429
|
+
params['optionTypes'] = prompt_results[:data] unless prompt_results[:data].nil?
|
430
|
+
else
|
431
|
+
return 1, "failed to parse optionTypes"
|
432
|
+
end
|
433
|
+
end
|
434
|
+
# massage association params a bit
|
435
|
+
params['workflow'] = {'id' => params['workflow']} if params['workflow'] && !params['workflow'].is_a?(Hash)
|
436
|
+
params['blueprint'] = {'id' => params['blueprint']} if params['blueprint'] && !params['blueprint'].is_a?(Hash)
|
437
|
+
payload.deep_merge!({catalog_item_type_object_key => params})
|
438
|
+
if payload[catalog_item_type_object_key].empty? # || options[:no_prompt]
|
439
|
+
raise_command_error "Specify at least one option to update.\n#{optparse}"
|
440
|
+
end
|
441
|
+
end
|
442
|
+
@catalog_item_types_interface.setopts(options)
|
443
|
+
if options[:dry_run]
|
444
|
+
print_dry_run @catalog_item_types_interface.dry.update(catalog_item_type['id'], payload)
|
445
|
+
return
|
446
|
+
end
|
447
|
+
json_response = @catalog_item_types_interface.update(catalog_item_type['id'], payload)
|
448
|
+
catalog_item_type = json_response[catalog_item_type_object_key]
|
449
|
+
render_response(json_response, options, catalog_item_type_object_key) do
|
450
|
+
print_green_success "Updated catalog item type #{catalog_item_type['name']}"
|
451
|
+
return _get(catalog_item_type["id"], {}, options)
|
452
|
+
end
|
453
|
+
return 0, nil
|
454
|
+
end
|
455
|
+
|
456
|
+
def remove(args)
|
457
|
+
options = {}
|
458
|
+
params = {}
|
459
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
460
|
+
opts.banner = subcommand_usage("[type] [options]")
|
461
|
+
build_standard_remove_options(opts, options)
|
462
|
+
opts.footer = <<-EOT
|
463
|
+
Delete a catalog item type.
|
464
|
+
[type] is required. This is the name or id of a catalog item type.
|
465
|
+
EOT
|
466
|
+
end
|
467
|
+
optparse.parse!(args)
|
468
|
+
verify_args!(args:args, optparse:optparse, count:1)
|
469
|
+
connect(options)
|
470
|
+
catalog_item_type = find_catalog_item_type_by_name_or_id(args[0])
|
471
|
+
return 1 if catalog_item_type.nil?
|
472
|
+
@catalog_item_types_interface.setopts(options)
|
473
|
+
if options[:dry_run]
|
474
|
+
print_dry_run @catalog_item_types_interface.dry.destroy(catalog_item_type['id'], params)
|
475
|
+
return
|
476
|
+
end
|
477
|
+
unless options[:yes] || Morpheus::Cli::OptionTypes.confirm("Are you sure you want to delete the catalog item type #{catalog_item_type['name']}?")
|
478
|
+
return 9, "aborted command"
|
479
|
+
end
|
480
|
+
json_response = @catalog_item_types_interface.destroy(catalog_item_type['id'], params)
|
481
|
+
render_response(json_response, options) do
|
482
|
+
print_green_success "Removed catalog item type #{catalog_item_type['name']}"
|
483
|
+
end
|
484
|
+
return 0, nil
|
485
|
+
end
|
486
|
+
|
487
|
+
private
|
488
|
+
|
489
|
+
def catalog_item_type_column_definitions()
|
490
|
+
{
|
491
|
+
"ID" => 'id',
|
492
|
+
"Name" => 'name',
|
493
|
+
"Description" => 'description',
|
494
|
+
"Type" => lambda {|it| format_catalog_type(it) },
|
495
|
+
"Blueprint" => lambda {|it| it['blueprint'] ? it['blueprint']['name'] : nil },
|
496
|
+
"Workflow" => lambda {|it| it['workflow'] ? it['workflow']['name'] : nil },
|
497
|
+
"Context" => lambda {|it| it['context'] },
|
498
|
+
# "Content" => lambda {|it| it['content'] },
|
499
|
+
"Enabled" => lambda {|it| format_boolean(it['enabled']) },
|
500
|
+
"Featured" => lambda {|it| format_boolean(it['featured']) },
|
501
|
+
#"Config" => lambda {|it| it['config'] },
|
502
|
+
"Created" => lambda {|it| format_local_dt(it['dateCreated']) },
|
503
|
+
"Updated" => lambda {|it| format_local_dt(it['lastUpdated']) },
|
504
|
+
}
|
505
|
+
end
|
506
|
+
|
507
|
+
def format_catalog_type(catalog_item_type)
|
508
|
+
out = ""
|
509
|
+
# api "blueprint": {"name":my blueprint"} }
|
510
|
+
# instead of cryptic refType
|
511
|
+
if catalog_item_type['type']
|
512
|
+
if catalog_item_type['type'].is_a?(String)
|
513
|
+
out << catalog_item_type['type'].to_s.capitalize
|
514
|
+
else
|
515
|
+
out << (catalog_item_type['type']['name'] || catalog_item_type['type']['code']) rescue catalog_item_type['type'].to_s
|
516
|
+
end
|
517
|
+
else
|
518
|
+
# refType is not returned
|
519
|
+
ref_type = catalog_item_type['refType']
|
520
|
+
if ref_type == 'InstanceType'
|
521
|
+
out << "Instance"
|
522
|
+
elsif ref_type == 'AppTemplate'
|
523
|
+
out << "Blueprint"
|
524
|
+
elsif ref_type
|
525
|
+
out << ref_type
|
526
|
+
else
|
527
|
+
"(none)"
|
528
|
+
end
|
529
|
+
end
|
530
|
+
out
|
531
|
+
end
|
532
|
+
|
533
|
+
def add_catalog_item_type_option_types
|
534
|
+
[
|
535
|
+
{'code' => 'catalogItemType.type', 'shorthand' => '-t', 'fieldName' => 'type', 'fieldLabel' => 'Type', 'type' => 'select', 'selectOptions' => [{'name' => 'Instance', 'value' => 'instance'}, {'name' => 'Blueprint', 'value' => 'blueprint'}, {'name' => 'Workflow', 'value' => 'workflow'}], 'defaultValue' => 'instance', 'required' => true},
|
536
|
+
{'fieldName' => 'name', 'fieldLabel' => 'Name', 'type' => 'text', 'required' => true},
|
537
|
+
{'fieldName' => 'description', 'fieldLabel' => 'Description', 'type' => 'text'},
|
538
|
+
{'fieldName' => 'enabled', 'fieldLabel' => 'Enabled', 'type' => 'checkbox', 'defaultValue' => true},
|
539
|
+
{'fieldName' => 'featured', 'fieldLabel' => 'Featured', 'type' => 'checkbox', 'defaultValue' => false},
|
540
|
+
{'fieldName' => 'visibility', 'fieldLabel' => 'Visibility', 'type' => 'select', 'selectOptions' => [{'name' => 'Private', 'value' => 'private'}, {'name' => 'Public', 'value' => 'public'}], 'defaultValue' => 'private', 'required' => true},
|
541
|
+
{'fieldName' => 'iconPath', 'fieldLabel' => 'Logo', 'type' => 'select', 'optionSource' => 'iconList'},
|
542
|
+
#{'fieldName' => 'optionTypes', 'fieldLabel' => 'Option Types', 'type' => 'text', 'description' => 'Option Types to include, comma separated list of names or IDs.'},
|
543
|
+
{'dependsOnCode' => 'catalogItemType.type:instance', 'fieldName' => 'config', 'fieldLabel' => 'Config', 'type' => 'code-editor', 'description' => 'JSON or YAML', 'required' => true},
|
544
|
+
{'dependsOnCode' => 'catalogItemType.type:blueprint', 'fieldName' => 'blueprint', 'fieldLabel' => 'Blueprint', 'type' => 'select', 'optionSource' => 'blueprints', 'description' => 'Choose a blueprint to apply to the catalog item.', 'required' => true, 'noParams' => true},
|
545
|
+
{'dependsOnCode' => 'catalogItemType.type:blueprint', 'fieldName' => 'appSpec', 'fieldLabel' => 'App Spec', 'type' => 'code-editor', 'description' => 'Enter a spec in the for the App, the Scribe YAML format', 'required' => true},
|
546
|
+
{'dependsOnCode' => 'catalogItemType.type:workflow', 'fieldName' => 'workflow', 'fieldLabel' => 'Workflow', 'type' => 'select', 'optionSource' => 'operationWorkflows', 'description' => 'Enter a spec in the for the App, the Scribe YAML format', 'noParams' => true},
|
547
|
+
{'dependsOnCode' => 'catalogItemType.type:workflow', 'fieldName' => 'context', 'fieldLabel' => 'Context Type', 'type' => 'select', 'optionSource' => lambda { |api_client, api_params|
|
548
|
+
[{'name' => "Select", 'value' => ""}, {'name' => "None", 'value' => "appliance"}, {'name' => "Instance", 'value' => "instance"}, {'name' => "Server", 'value' => "server"}]
|
549
|
+
}, 'description' => 'Context for operational workflow, determines target type', 'defaultValue' => 'Select', 'required' => false},
|
550
|
+
{'fieldName' => 'content', 'fieldLabel' => 'Content', 'type' => 'code-editor', 'description' => 'Wiki Page Content describing the catalog item'}
|
551
|
+
]
|
552
|
+
end
|
553
|
+
|
554
|
+
def add_catalog_item_type_advanced_option_types
|
555
|
+
[]
|
556
|
+
end
|
557
|
+
|
558
|
+
def update_catalog_item_type_option_types
|
559
|
+
list = add_catalog_item_type_option_types.collect {|it|
|
560
|
+
it.delete('required')
|
561
|
+
it.delete('defaultValue')
|
562
|
+
it
|
563
|
+
}
|
564
|
+
list = list.reject {|it| ["type"].include? it['fieldName'] }
|
565
|
+
list
|
566
|
+
end
|
567
|
+
|
568
|
+
def update_catalog_item_type_advanced_option_types
|
569
|
+
add_catalog_item_type_advanced_option_types.collect {|it|
|
570
|
+
it.delete('required')
|
571
|
+
it.delete('defaultValue')
|
572
|
+
it
|
573
|
+
}
|
574
|
+
end
|
575
|
+
|
576
|
+
def catalog_item_type_object_key
|
577
|
+
'catalogItemType'
|
578
|
+
end
|
579
|
+
|
580
|
+
def catalog_item_type_list_key
|
581
|
+
'catalogItemTypes'
|
582
|
+
end
|
583
|
+
|
584
|
+
def find_catalog_item_type_by_name_or_id(val)
|
585
|
+
if val.to_s =~ /\A\d{1,}\Z/
|
586
|
+
return find_catalog_item_type_by_id(val)
|
587
|
+
else
|
588
|
+
return find_catalog_item_type_by_name(val)
|
589
|
+
end
|
590
|
+
end
|
591
|
+
|
592
|
+
def find_catalog_item_type_by_id(id)
|
593
|
+
begin
|
594
|
+
json_response = @catalog_item_types_interface.get(id.to_i)
|
595
|
+
return json_response[catalog_item_type_object_key]
|
596
|
+
rescue RestClient::Exception => e
|
597
|
+
if e.response && e.response.code == 404
|
598
|
+
print_red_alert "catalog item type not found by id '#{id}'"
|
599
|
+
else
|
600
|
+
raise e
|
601
|
+
end
|
602
|
+
end
|
603
|
+
end
|
604
|
+
|
605
|
+
def find_catalog_item_type_by_name(name)
|
606
|
+
json_response = @catalog_item_types_interface.list({name: name.to_s})
|
607
|
+
catalog_item_types = json_response[catalog_item_type_list_key]
|
608
|
+
if catalog_item_types.empty?
|
609
|
+
print_red_alert "catalog item type not found by name '#{name}'"
|
610
|
+
return nil
|
611
|
+
elsif catalog_item_types.size > 1
|
612
|
+
print_red_alert "#{catalog_item_types.size} catalog item types found by name '#{name}'"
|
613
|
+
puts_error as_pretty_table(catalog_item_types, [:id, :name], {color:red})
|
614
|
+
print_red_alert "Try using ID instead"
|
615
|
+
print reset,"\n"
|
616
|
+
return nil
|
617
|
+
else
|
618
|
+
return catalog_item_types[0]
|
619
|
+
end
|
620
|
+
end
|
621
|
+
|
622
|
+
end
|