morpheus-cli 5.5.0 → 5.5.1.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.
- checksums.yaml +4 -4
- data/Dockerfile +2 -2
- data/lib/morpheus/api/api_client.rb +12 -0
- data/lib/morpheus/api/backup_service_types_interface.rb +9 -0
- data/lib/morpheus/api/backup_services_interface.rb +9 -0
- data/lib/morpheus/api/clusters_interface.rb +12 -0
- data/lib/morpheus/api/network_pool_servers_interface.rb +7 -0
- data/lib/morpheus/api/scale_thresholds_interface.rb +9 -0
- data/lib/morpheus/cli/cli_command.rb +39 -20
- data/lib/morpheus/cli/commands/apps.rb +1 -1
- data/lib/morpheus/cli/commands/backup_services_command.rb +44 -0
- data/lib/morpheus/cli/commands/cloud_resource_pools_command.rb +33 -2
- data/lib/morpheus/cli/commands/clouds.rb +18 -6
- data/lib/morpheus/cli/commands/clusters.rb +66 -5
- data/lib/morpheus/cli/commands/hosts.rb +5 -1
- data/lib/morpheus/cli/commands/instances.rb +1 -1
- data/lib/morpheus/cli/commands/integrations_command.rb +1 -1
- data/lib/morpheus/cli/commands/invoices_command.rb +8 -1
- data/lib/morpheus/cli/commands/jobs_command.rb +45 -225
- data/lib/morpheus/cli/commands/library_container_types_command.rb +52 -3
- data/lib/morpheus/cli/commands/library_option_types_command.rb +56 -62
- data/lib/morpheus/cli/commands/load_balancers.rb +11 -19
- data/lib/morpheus/cli/commands/network_pool_servers_command.rb +5 -2
- data/lib/morpheus/cli/commands/roles.rb +475 -70
- data/lib/morpheus/cli/commands/scale_thresholds.rb +103 -0
- data/lib/morpheus/cli/commands/tasks.rb +19 -12
- data/lib/morpheus/cli/commands/user_sources_command.rb +107 -39
- data/lib/morpheus/cli/commands/users.rb +10 -10
- data/lib/morpheus/cli/commands/view.rb +1 -0
- data/lib/morpheus/cli/commands/workflows.rb +21 -14
- data/lib/morpheus/cli/error_handler.rb +13 -4
- data/lib/morpheus/cli/mixins/accounts_helper.rb +1 -1
- data/lib/morpheus/cli/mixins/execution_request_helper.rb +1 -1
- data/lib/morpheus/cli/mixins/infrastructure_helper.rb +4 -3
- data/lib/morpheus/cli/mixins/jobs_helper.rb +173 -0
- data/lib/morpheus/cli/mixins/print_helper.rb +120 -38
- data/lib/morpheus/cli/mixins/provisioning_helper.rb +1 -3
- data/lib/morpheus/cli/mixins/rest_command.rb +58 -15
- data/lib/morpheus/cli/option_types.rb +68 -37
- data/lib/morpheus/cli/version.rb +1 -1
- data/lib/morpheus/logging.rb +6 -8
- metadata +9 -4
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
require 'morpheus/cli/mixins/print_helper'
|
|
2
|
+
# Mixin for Morpheus::Cli command classes
|
|
3
|
+
# Provides refreshing job execution records by id
|
|
4
|
+
module Morpheus::Cli::JobsHelper
|
|
5
|
+
|
|
6
|
+
def self.included(klass)
|
|
7
|
+
klass.send :include, Morpheus::Cli::PrintHelper
|
|
8
|
+
# klass.send :include, Morpheus::Cli::ProcessesHelper
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def api_client
|
|
12
|
+
raise "#{self.class} has not defined @api_client" if @api_client.nil?
|
|
13
|
+
@api_client
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def jobs_interface
|
|
17
|
+
# get_interface('jobs')
|
|
18
|
+
api_client.jobs
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def get_process_event_data(process_or_event)
|
|
22
|
+
{
|
|
23
|
+
id: process_or_event['id'],
|
|
24
|
+
description: process_or_event['description'] || (process_or_event['refType'] == 'instance' ? process_or_event['displayName'] : (process_or_event['processTypeName'] || '').capitalize),
|
|
25
|
+
start_date: format_local_dt(process_or_event['startDate']),
|
|
26
|
+
created_by: process_or_event['createdBy'] ? process_or_event['createdBy']['displayName'] : '',
|
|
27
|
+
duration: format_human_duration((process_or_event['duration'] || process_or_event['statusEta'] || 0) / 1000.0),
|
|
28
|
+
status: format_job_status(process_or_event['status']),
|
|
29
|
+
error: process_or_event['message'] || process_or_event['error'],
|
|
30
|
+
output: process_or_event['output'],
|
|
31
|
+
}
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# both process and process events
|
|
35
|
+
def print_process_events(events, options={})
|
|
36
|
+
# event_columns = [:id, :description, :start_date, :created_by, :duration, :status, :error, :output]
|
|
37
|
+
event_columns = {
|
|
38
|
+
"ID" => lambda {|it| it[:id]},
|
|
39
|
+
"Description" => lambda {|it| it[:description]},
|
|
40
|
+
"Start Date" => lambda {|it| it[:start_date]},
|
|
41
|
+
"Created By" => lambda {|it| it[:created_by]},
|
|
42
|
+
"Duration" => lambda {|it| it[:duration]},
|
|
43
|
+
"Status" => lambda {|it| it[:status]},
|
|
44
|
+
"Error" => lambda {|it| options[:details] ? it[:error] : truncate_string(it[:error], 32) },
|
|
45
|
+
"Output" => lambda {|it| options[:details] ? it[:output] : truncate_string(it[:output], 32) }
|
|
46
|
+
}
|
|
47
|
+
print as_pretty_table(events.collect {|it| get_process_event_data(it)}, event_columns.upcase_keys!, options)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def print_job_execution(job_execution, options)
|
|
51
|
+
process = job_execution['process']
|
|
52
|
+
print cyan
|
|
53
|
+
description_cols = {
|
|
54
|
+
"ID" => lambda {|it| it['id'] },
|
|
55
|
+
"Job" => lambda {|it| it['job'] ? it['job']['name'] : ''},
|
|
56
|
+
"Job Type" => lambda {|it| it['job'] && it['job']['type'] ? (it['job']['type']['code'] == 'morpheus.workflow' ? 'Workflow' : 'Task') : ''},
|
|
57
|
+
# "Description" => lambda {|it| it['description'] || (it['job'] ? it['job']['description'] : '') },
|
|
58
|
+
"Start Date" => lambda {|it| format_local_dt(it['startDate'])},
|
|
59
|
+
"ETA/Time" => lambda {|it| it['duration'] ? format_human_duration(it['duration'] / 1000.0) : ''},
|
|
60
|
+
"Status" => lambda {|it| format_job_status(it['status'])},
|
|
61
|
+
#"Output" => lambda {|it| it['process'] && (it['process']['output']) ?(it['process']['output']).to_s.strip : ''},
|
|
62
|
+
#"Error" => lambda {|it| it['process'] && (it['process']['message'] || it['process']['error']) ? red + (it['process']['message'] || it['process']['error']).to_s.strip + cyan : ''},
|
|
63
|
+
"Created By" => lambda {|it| it['createdBy'].nil? ? '' : it['createdBy']['displayName'] || it['createdBy']['username']}
|
|
64
|
+
}
|
|
65
|
+
print_description_list(description_cols, job_execution, options)
|
|
66
|
+
|
|
67
|
+
if process
|
|
68
|
+
process_data = get_process_event_data(process)
|
|
69
|
+
print_h2 "Process Details"
|
|
70
|
+
process_description_cols = {
|
|
71
|
+
"Process ID" => lambda {|it| it[:id]},
|
|
72
|
+
"Description" => lambda {|it| it[:description]},
|
|
73
|
+
"Start Date" => lambda {|it| it[:start_date]},
|
|
74
|
+
"Created By" => lambda {|it| it[:created_by]},
|
|
75
|
+
"Duration" => lambda {|it| it[:duration]},
|
|
76
|
+
"Status" => lambda {|it| it[:status]}
|
|
77
|
+
}
|
|
78
|
+
print_description_list(process_description_cols, process_data, options)
|
|
79
|
+
|
|
80
|
+
if process_data[:output] && process_data[:output].strip.length > 0
|
|
81
|
+
print_h2 "Output"
|
|
82
|
+
print process['output']
|
|
83
|
+
end
|
|
84
|
+
if process_data[:error] && process_data[:error].strip.length > 0
|
|
85
|
+
print_h2 "Error"
|
|
86
|
+
print process['message'] || process['error']
|
|
87
|
+
print reset,"\n"
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
if process['events'] && !process['events'].empty?
|
|
92
|
+
print_h2 "Process Events", options
|
|
93
|
+
print_process_events(process['events'], options)
|
|
94
|
+
end
|
|
95
|
+
else
|
|
96
|
+
print reset,"\n"
|
|
97
|
+
end
|
|
98
|
+
return 0, nil
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def print_job_executions(execs, options={})
|
|
102
|
+
if execs.empty?
|
|
103
|
+
print cyan,"No job executions found.",reset,"\n"
|
|
104
|
+
else
|
|
105
|
+
rows = execs.collect do |ex|
|
|
106
|
+
{
|
|
107
|
+
id: ex['id'],
|
|
108
|
+
job: ex['job'] ? ex['job']['name'] : '',
|
|
109
|
+
description: ex['description'] || ex['job'] ? ex['job']['description'] : '',
|
|
110
|
+
type: ex['job'] && ex['job']['type'] ? (ex['job']['type']['code'] == 'morpheus.workflow' ? 'Workflow' : 'Task') : '',
|
|
111
|
+
start: format_local_dt(ex['startDate']),
|
|
112
|
+
duration: ex['duration'] ? format_human_duration(ex['duration'] / 1000.0) : '',
|
|
113
|
+
status: format_job_status(ex['status']),
|
|
114
|
+
error: truncate_string(ex['process'] && (ex['process']['message'] || ex['process']['error']) ? ex['process']['message'] || ex['process']['error'] : '', options[:details] ? nil : 32),
|
|
115
|
+
output: truncate_string(ex['process'] && ex['process']['output'] ? ex['process']['output'] : '', options[:details] ? nil : 32),
|
|
116
|
+
}
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
columns = [
|
|
120
|
+
:id, :job, :type, {'START DATE' => :start}, {'ETA/TIME' => :duration}, :status, :error, :output
|
|
121
|
+
]
|
|
122
|
+
print as_pretty_table(rows, columns, options)
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def format_job_status(status_string, return_color=cyan)
|
|
127
|
+
out = ""
|
|
128
|
+
if status_string
|
|
129
|
+
if ['complete','success', 'successful', 'ok'].include?(status_string)
|
|
130
|
+
out << "#{green}#{status_string.upcase}"
|
|
131
|
+
elsif ['error', 'offline', 'failed', 'failure'].include?(status_string)
|
|
132
|
+
out << "#{red}#{status_string.upcase}"
|
|
133
|
+
else
|
|
134
|
+
out << "#{yellow}#{status_string.upcase}"
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
out + return_color
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# this is built into CliCommand now...
|
|
141
|
+
# def find_by_name_or_id(type, val)
|
|
142
|
+
# interface = instance_variable_get "@#{type}s_interface"
|
|
143
|
+
# typeCamelCase = type.gsub(/(?:^|_)([a-z])/) do $1.upcase end
|
|
144
|
+
# typeCamelCase = typeCamelCase[0, 1].downcase + typeCamelCase[1..-1]
|
|
145
|
+
# (val.to_s =~ /\A\d{1,}\Z/) ? interface.get(val.to_i)[typeCamelCase] : interface.list({'name' => val})["#{typeCamelCase}s"].first
|
|
146
|
+
# end
|
|
147
|
+
|
|
148
|
+
# refresh execution request until it is finished
|
|
149
|
+
# returns json response data of the last execution request when status reached 'completed' or 'failed'
|
|
150
|
+
def wait_for_job_execution(job_execution_id, options={}, print_output = true)
|
|
151
|
+
refresh_interval = 10
|
|
152
|
+
if options[:refresh_interval].to_i > 0
|
|
153
|
+
refresh_interval = options[:refresh_interval]
|
|
154
|
+
end
|
|
155
|
+
refresh_display_seconds = refresh_interval % 1.0 == 0 ? refresh_interval.to_i : refresh_interval
|
|
156
|
+
unless options[:quiet]
|
|
157
|
+
print cyan, "Refreshing every #{refresh_display_seconds} seconds until execution is complete...", "\n", reset
|
|
158
|
+
end
|
|
159
|
+
job_execution = jobs_interface.get_execution(job_execution_id)['jobExecution']
|
|
160
|
+
while ['new','queued','pending','running'].include?(job_execution['status']) do
|
|
161
|
+
sleep(refresh_interval)
|
|
162
|
+
job_execution = jobs_interface.get_execution(job_execution_id)['jobExecution']
|
|
163
|
+
end
|
|
164
|
+
if print_output && options[:quiet] != true
|
|
165
|
+
print_h1 "Morpheus Job Execution", [], options
|
|
166
|
+
print_job_execution(job_execution, options)
|
|
167
|
+
end
|
|
168
|
+
return job_execution
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
end
|
|
@@ -608,6 +608,9 @@ module Morpheus::Cli::PrintHelper
|
|
|
608
608
|
# @param suffix [String] the character to pad right side with. Default is '...'
|
|
609
609
|
def truncate_string(value, width, suffix="...")
|
|
610
610
|
value = value.to_s
|
|
611
|
+
if !width
|
|
612
|
+
return value
|
|
613
|
+
end
|
|
611
614
|
# JD: hack alerty.. this sux, but it's a best effort to preserve values containing ascii coloring codes
|
|
612
615
|
# it stops working when there are words separated by ascii codes, eg. two diff colors
|
|
613
616
|
# plus this is probably pretty slow...
|
|
@@ -714,7 +717,11 @@ module Morpheus::Cli::PrintHelper
|
|
|
714
717
|
|
|
715
718
|
# support --fields x,y,z and --all-fields or --fields all
|
|
716
719
|
all_fields = data.first ? data.first.keys : []
|
|
717
|
-
|
|
720
|
+
#todo: support --raw-fields meh, not really needed..
|
|
721
|
+
# if options[:include_fields] && options[:raw_fields]
|
|
722
|
+
# data = transform_data_for_field_options(data, options)
|
|
723
|
+
# data = data[options[:include_fields_context]] if options[:include_fields_context]
|
|
724
|
+
# elsif options[:include_fields]
|
|
718
725
|
if options[:include_fields]
|
|
719
726
|
if (options[:include_fields].is_a?(Array) && options[:include_fields].size == 1 && options[:include_fields][0] == 'all') || options[:include_fields] == 'all'
|
|
720
727
|
columns = all_fields
|
|
@@ -1093,16 +1100,78 @@ module Morpheus::Cli::PrintHelper
|
|
|
1093
1100
|
'"' + v.to_s.gsub('"', '""') + '"'
|
|
1094
1101
|
end
|
|
1095
1102
|
|
|
1096
|
-
def as_csv(data,
|
|
1103
|
+
def as_csv(data, default_columns=nil, options={}, object_key=nil)
|
|
1097
1104
|
out = ""
|
|
1098
1105
|
delim = options[:csv_delim] || options[:delim] || ","
|
|
1099
1106
|
newline = options[:csv_newline] || options[:newline] || "\n"
|
|
1100
1107
|
include_header = options[:csv_no_header] ? false : true
|
|
1101
1108
|
do_quotes = options[:csv_quotes] || options[:quotes]
|
|
1102
1109
|
|
|
1110
|
+
if options[:include_fields]
|
|
1111
|
+
data = transform_data_for_field_options(data, options, object_key)
|
|
1112
|
+
if data.is_a?(Hash)
|
|
1113
|
+
if options[:raw_fields]
|
|
1114
|
+
if options[:include_fields_context]
|
|
1115
|
+
data = data[options[:include_fields_context]]
|
|
1116
|
+
else
|
|
1117
|
+
# todo: could use a dynamic object_key, first Array or Hash in the data, can probably always do this...
|
|
1118
|
+
# if object_key.nil?
|
|
1119
|
+
# object_key = data.keys.find {|k| data[k].is_a?(Array) || data[k].is_a?(Hash) }
|
|
1120
|
+
# end
|
|
1121
|
+
# if object_key && data[object_key]
|
|
1122
|
+
# data = data[object_key]
|
|
1123
|
+
# end
|
|
1124
|
+
end
|
|
1125
|
+
else
|
|
1126
|
+
if object_key
|
|
1127
|
+
data = data[object_key]
|
|
1128
|
+
end
|
|
1129
|
+
end
|
|
1130
|
+
end
|
|
1131
|
+
else
|
|
1132
|
+
# need array of records, so always select the object/array here
|
|
1133
|
+
if object_key
|
|
1134
|
+
if data.is_a?(Hash)
|
|
1135
|
+
data = data[object_key]
|
|
1136
|
+
else
|
|
1137
|
+
Morpheus::Logging::DarkPrinter.puts "as_csv() expects data as an to fetch object key '#{object_key}' from, #{records.class}." if Morpheus::Logging.debug?
|
|
1138
|
+
end
|
|
1139
|
+
end
|
|
1140
|
+
end
|
|
1141
|
+
|
|
1142
|
+
|
|
1143
|
+
records = data
|
|
1144
|
+
|
|
1145
|
+
# allow records as Array and Hash only..
|
|
1146
|
+
if records.is_a?(Array)
|
|
1147
|
+
# records = records
|
|
1148
|
+
elsif records.is_a?(Hash)
|
|
1149
|
+
records = [records]
|
|
1150
|
+
else
|
|
1151
|
+
#raise "records_as_csv expects records as an Array of objects to render"
|
|
1152
|
+
Morpheus::Logging::DarkPrinter.puts "as_csv() expects data as an Array of objects to render, got a #{records.class} instead" if Morpheus::Logging.debug?
|
|
1153
|
+
# return ""
|
|
1154
|
+
return out
|
|
1155
|
+
end
|
|
1156
|
+
|
|
1157
|
+
# build column definitions, by default use all properties for the first record (Hash) in the array
|
|
1158
|
+
columns = []
|
|
1159
|
+
all_fields = records.first.is_a?(Hash) ? records.first.keys : []
|
|
1160
|
+
if options[:include_fields]
|
|
1161
|
+
if (options[:include_fields].is_a?(Array) && options[:include_fields].size == 1 && options[:include_fields][0] == 'all') || options[:include_fields] == 'all'
|
|
1162
|
+
columns = all_fields
|
|
1163
|
+
else
|
|
1164
|
+
columns = options[:include_fields]
|
|
1165
|
+
end
|
|
1166
|
+
elsif options[:all_fields]
|
|
1167
|
+
columns = all_fields
|
|
1168
|
+
elsif default_columns
|
|
1169
|
+
columns = default_columns
|
|
1170
|
+
else
|
|
1171
|
+
columns = all_fields
|
|
1172
|
+
end
|
|
1173
|
+
|
|
1103
1174
|
column_defs = build_column_definitions(columns)
|
|
1104
|
-
#columns = columns.flatten.compact
|
|
1105
|
-
data_array = [data].flatten.compact
|
|
1106
1175
|
|
|
1107
1176
|
if include_header
|
|
1108
1177
|
headers = column_defs.collect {|column_def| column_def.label }
|
|
@@ -1113,7 +1182,7 @@ module Morpheus::Cli::PrintHelper
|
|
|
1113
1182
|
out << newline
|
|
1114
1183
|
end
|
|
1115
1184
|
lines = []
|
|
1116
|
-
|
|
1185
|
+
records.each do |obj|
|
|
1117
1186
|
if obj
|
|
1118
1187
|
cells = []
|
|
1119
1188
|
column_defs.each do |column_def|
|
|
@@ -1137,29 +1206,9 @@ module Morpheus::Cli::PrintHelper
|
|
|
1137
1206
|
|
|
1138
1207
|
end
|
|
1139
1208
|
|
|
1209
|
+
# deprecated, replaced by as_csv(records, columns, options, object_key)
|
|
1140
1210
|
def records_as_csv(records, options={}, default_columns=nil)
|
|
1141
|
-
|
|
1142
|
-
if !records
|
|
1143
|
-
#raise "records_as_csv expects records as an Array of objects to render"
|
|
1144
|
-
return out
|
|
1145
|
-
end
|
|
1146
|
-
cols = []
|
|
1147
|
-
all_fields = records.first ? records.first.keys : []
|
|
1148
|
-
if options[:include_fields]
|
|
1149
|
-
if (options[:include_fields].is_a?(Array) && options[:include_fields].size == 1 && options[:include_fields][0] == 'all') || options[:include_fields] == 'all'
|
|
1150
|
-
cols = all_fields
|
|
1151
|
-
else
|
|
1152
|
-
cols = options[:include_fields]
|
|
1153
|
-
end
|
|
1154
|
-
elsif options[:all_fields]
|
|
1155
|
-
cols = all_fields
|
|
1156
|
-
elsif default_columns
|
|
1157
|
-
cols = default_columns
|
|
1158
|
-
else
|
|
1159
|
-
cols = all_fields
|
|
1160
|
-
end
|
|
1161
|
-
out << as_csv(records, cols, options)
|
|
1162
|
-
out
|
|
1211
|
+
as_csv(records, default_columns, options)
|
|
1163
1212
|
end
|
|
1164
1213
|
|
|
1165
1214
|
def as_json(data, options={}, object_key=nil)
|
|
@@ -1169,12 +1218,7 @@ module Morpheus::Cli::PrintHelper
|
|
|
1169
1218
|
end
|
|
1170
1219
|
|
|
1171
1220
|
if options[:include_fields]
|
|
1172
|
-
|
|
1173
|
-
# data[object_key] = filter_data(data[object_key], options[:include_fields])
|
|
1174
|
-
data = {(object_key) => filter_data(data[object_key], options[:include_fields]) }
|
|
1175
|
-
else
|
|
1176
|
-
data = filter_data(data, options[:include_fields])
|
|
1177
|
-
end
|
|
1221
|
+
data = transform_data_for_field_options(data, options, object_key)
|
|
1178
1222
|
end
|
|
1179
1223
|
|
|
1180
1224
|
do_pretty = options.key?(:pretty_json) ? options[:pretty_json] : true
|
|
@@ -1193,11 +1237,7 @@ module Morpheus::Cli::PrintHelper
|
|
|
1193
1237
|
return "null" # "No data"
|
|
1194
1238
|
end
|
|
1195
1239
|
if options[:include_fields]
|
|
1196
|
-
|
|
1197
|
-
data[object_key] = filter_data(data[object_key], options[:include_fields])
|
|
1198
|
-
else
|
|
1199
|
-
data = filter_data(data, options[:include_fields])
|
|
1200
|
-
end
|
|
1240
|
+
data = transform_data_for_field_options(data, options, object_key)
|
|
1201
1241
|
end
|
|
1202
1242
|
begin
|
|
1203
1243
|
out << data.to_yaml
|
|
@@ -1209,6 +1249,48 @@ module Morpheus::Cli::PrintHelper
|
|
|
1209
1249
|
out
|
|
1210
1250
|
end
|
|
1211
1251
|
|
|
1252
|
+
|
|
1253
|
+
# transform data for options --fields id,authority and --select id,authority
|
|
1254
|
+
# support traversing records with --raw-fields and the list command. Example: roles list --raw-fields roles.id,roles.authority
|
|
1255
|
+
def transform_data_for_field_options(data, options, object_key=nil)
|
|
1256
|
+
if options[:include_fields] && data.is_a?(Hash)
|
|
1257
|
+
if options[:raw_fields]
|
|
1258
|
+
row = (object_key && !options[:raw_fields]) ? data[object_key] : data
|
|
1259
|
+
records = [row].flatten()
|
|
1260
|
+
# look for an array in the first field only now...
|
|
1261
|
+
field_parts = options[:include_fields][0].to_s.split(".")
|
|
1262
|
+
field_context = field_parts[0]
|
|
1263
|
+
context_data = data[field_context]
|
|
1264
|
+
if field_parts.size > 1 && context_data.is_a?(Array)
|
|
1265
|
+
# inject all the root level properties to be selectable too..
|
|
1266
|
+
context_data = data.delete(field_context)
|
|
1267
|
+
# records = context_data
|
|
1268
|
+
records = context_data.collect {|it| it.is_a?(Hash) ? data.merge(it) : data }
|
|
1269
|
+
# hacky modifying options in place
|
|
1270
|
+
options[:include_fields_context] = field_context
|
|
1271
|
+
options[:include_fields] = options[:include_fields].collect {|it| it.sub(field_context+'.', '')}
|
|
1272
|
+
# data = filter_data(records, options[:include_fields])
|
|
1273
|
+
# data[field_context] = filter_data(records, options[:include_fields])
|
|
1274
|
+
data = {(field_context) => filter_data(records, options[:include_fields])}
|
|
1275
|
+
else
|
|
1276
|
+
data = filter_data(data, options[:include_fields])
|
|
1277
|
+
end
|
|
1278
|
+
else
|
|
1279
|
+
# By default, fields are relative to the object_key, so you can use -F id instead of requiring -F instance.id
|
|
1280
|
+
# So ironically it is the 'raw' options (:raw_fields == true) that has to do all this funny stuff to filter intuitively.
|
|
1281
|
+
if object_key
|
|
1282
|
+
# this removes everything but the object, makes sense when using --fields
|
|
1283
|
+
data = {(object_key) => filter_data(data[object_key], options[:include_fields])}
|
|
1284
|
+
# this preserves other fields eg. meta...
|
|
1285
|
+
# data[object_key] = filter_data(data[object_key], options[:include_fields])
|
|
1286
|
+
else
|
|
1287
|
+
data = filter_data(data, options[:include_fields])
|
|
1288
|
+
end
|
|
1289
|
+
end
|
|
1290
|
+
end
|
|
1291
|
+
return data
|
|
1292
|
+
end
|
|
1293
|
+
|
|
1212
1294
|
def sleep_with_dots(sleep_seconds, dots=3, dot_chr=".")
|
|
1213
1295
|
dot_interval = (sleep_seconds.to_f / dots.to_i)
|
|
1214
1296
|
dots.to_i.times do |dot_index|
|
|
@@ -2082,7 +2082,6 @@ module Morpheus::Cli::ProvisioningHelper
|
|
|
2082
2082
|
if !visibility && !options[:no_prompt]
|
|
2083
2083
|
visibility = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'visibility', 'fieldLabel' => 'Tenant Permissions Visibility', 'type' => 'select', 'defaultValue' => 'private', 'required' => true, 'selectOptions' => [{'name' => 'Private', 'value' => 'private'},{'name' => 'Public', 'value' => 'public'}]}], options[:options], @api_client, {})['visibility']
|
|
2084
2084
|
end
|
|
2085
|
-
|
|
2086
2085
|
permissions['resourcePool'] = {'visibility' => visibility} if visibility
|
|
2087
2086
|
end
|
|
2088
2087
|
|
|
@@ -2114,8 +2113,7 @@ module Morpheus::Cli::ProvisioningHelper
|
|
|
2114
2113
|
def prompt_permissions_v2(options, excludes = [])
|
|
2115
2114
|
perms = prompt_permissions(options, excludes)
|
|
2116
2115
|
rtn = {}
|
|
2117
|
-
|
|
2118
|
-
rtn['visibility'] = perms['resourcePool']['visibility'] if !perms['resourcePool'].nil?
|
|
2116
|
+
rtn['visibility'] = perms['resourcePool']['visibility'] if !perms['resourcePool'].nil? && !excludes.include?('visibility')
|
|
2119
2117
|
rtn['tenants'] = ((perms['tenantPermissions'] || {})['accounts'] || []).collect {|it| {'id' => it}}
|
|
2120
2118
|
rtn
|
|
2121
2119
|
end
|
|
@@ -148,6 +148,28 @@ module Morpheus::Cli::RestCommand
|
|
|
148
148
|
|
|
149
149
|
alias :set_rest_interface_name :rest_interface_name=
|
|
150
150
|
|
|
151
|
+
# rest_perms_config enables and configures permissions prompt
|
|
152
|
+
def rest_perms_config
|
|
153
|
+
@rest_perms_config || {}
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def rest_perms_config=(v)
|
|
157
|
+
@rest_perms_config = v
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
alias :set_rest_perms_config :rest_perms_config=
|
|
161
|
+
|
|
162
|
+
# rest_option_context_map specifies context mapping during option prompt. default is domain => ''
|
|
163
|
+
def rest_option_context_map
|
|
164
|
+
@option_context_map || {'domain' => ''}
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def rest_option_context_map=(v)
|
|
168
|
+
@option_context_map = v
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
alias :set_rest_option_context_map :rest_option_context_map=
|
|
172
|
+
|
|
151
173
|
# rest_has_type indicates a resource has a type. default is false
|
|
152
174
|
def rest_has_type
|
|
153
175
|
@rest_has_type == true
|
|
@@ -329,6 +351,14 @@ module Morpheus::Cli::RestCommand
|
|
|
329
351
|
self.class.rest_interface_name
|
|
330
352
|
end
|
|
331
353
|
|
|
354
|
+
def rest_perms_config
|
|
355
|
+
self.class.rest_perms_config
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
def rest_option_context_map
|
|
359
|
+
self.class.rest_option_context_map
|
|
360
|
+
end
|
|
361
|
+
|
|
332
362
|
# returns the default rest interface, allows using rest_interface_name = "your"
|
|
333
363
|
# or override this method to return @your_interface if needed
|
|
334
364
|
def rest_interface
|
|
@@ -567,7 +597,8 @@ EOT
|
|
|
567
597
|
def add(args)
|
|
568
598
|
record_type = nil
|
|
569
599
|
record_type_id = nil
|
|
570
|
-
options = {}
|
|
600
|
+
options = {:options => {:context_map => rest_option_context_map}}
|
|
601
|
+
#respond_to?("#{rest_key}_option_context_map", true) ? send("#{rest_key}_option_context_map") : {'domain' => ''}}}
|
|
571
602
|
option_types = respond_to?("add_#{rest_key}_option_types", true) ? send("add_#{rest_key}_option_types") : []
|
|
572
603
|
advanced_option_types = respond_to?("add_#{rest_key}_advanced_option_types", true) ? send("add_#{rest_key}_advanced_option_types") : []
|
|
573
604
|
type_option_type = option_types.find {|it| it['fieldName'] == 'type'}
|
|
@@ -600,25 +631,25 @@ EOT
|
|
|
600
631
|
verify_args!(args:args, optparse:optparse, count: 0)
|
|
601
632
|
end
|
|
602
633
|
connect(options)
|
|
603
|
-
# load or prompt for type
|
|
604
|
-
if rest_has_type && type_option_type.nil?
|
|
605
|
-
if record_type_id.nil?
|
|
606
|
-
#raise_command_error "#{rest_type_label} is required.\n#{optparse}"
|
|
607
|
-
type_list = rest_type_interface.list({max:10000, creatable:true})[rest_type_list_key]
|
|
608
|
-
type_dropdown_options = respond_to?("#{rest_key}_type_list_to_options", true) ? send("#{rest_key}_type_list_to_options", type_list) : type_list.collect {|it| {'name' => it['name'], 'value' => it['code']} }
|
|
609
|
-
record_type_id = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'type', 'fieldLabel' => rest_type_label, 'type' => 'select', 'selectOptions' => type_dropdown_options, 'required' => true}], options[:options], @api_client)['type']
|
|
610
|
-
end
|
|
611
|
-
record_type = rest_type_find_by_name_or_id(record_type_id)
|
|
612
|
-
if record_type.nil?
|
|
613
|
-
return 1, "#{rest_type_label} not found for '#{record_type_id}"
|
|
614
|
-
end
|
|
615
|
-
end
|
|
616
634
|
passed_options = parse_passed_options(options)
|
|
617
635
|
payload = {}
|
|
618
636
|
if options[:payload]
|
|
619
637
|
payload = options[:payload]
|
|
620
638
|
payload.deep_merge!({rest_object_key => passed_options})
|
|
621
639
|
else
|
|
640
|
+
# load or prompt for type
|
|
641
|
+
if rest_has_type && type_option_type.nil?
|
|
642
|
+
if record_type_id.nil?
|
|
643
|
+
#raise_command_error "#{rest_type_label} is required.\n#{optparse}"
|
|
644
|
+
type_list = rest_type_interface.list({max:10000, creatable:true})[rest_type_list_key]
|
|
645
|
+
type_dropdown_options = respond_to?("#{rest_key}_type_list_to_options", true) ? send("#{rest_key}_type_list_to_options", type_list) : type_list.collect {|it| {'name' => it['name'], 'value' => it['code']} }
|
|
646
|
+
record_type_id = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'type', 'fieldLabel' => rest_type_label, 'type' => 'select', 'selectOptions' => type_dropdown_options, 'required' => true}], options[:options], @api_client)['type']
|
|
647
|
+
end
|
|
648
|
+
record_type = rest_type_find_by_name_or_id(record_type_id)
|
|
649
|
+
if record_type.nil?
|
|
650
|
+
return 1, "#{rest_type_label} not found for '#{record_type_id}"
|
|
651
|
+
end
|
|
652
|
+
end
|
|
622
653
|
record_payload = {}
|
|
623
654
|
if record_name
|
|
624
655
|
record_payload['name'] = record_name
|
|
@@ -667,7 +698,7 @@ EOT
|
|
|
667
698
|
end
|
|
668
699
|
end
|
|
669
700
|
api_params = (options[:params] || {}).merge(record_payload)
|
|
670
|
-
v_prompt = Morpheus::Cli::OptionTypes.prompt(my_option_types, options[:options], @api_client, api_params)
|
|
701
|
+
v_prompt = Morpheus::Cli::OptionTypes.prompt(my_option_types, options[:options], @api_client, api_params, false, true)
|
|
671
702
|
v_prompt.deep_compact!
|
|
672
703
|
v_prompt.booleanize! # 'on' => true
|
|
673
704
|
record_payload.deep_merge!(v_prompt)
|
|
@@ -679,6 +710,18 @@ EOT
|
|
|
679
710
|
v_prompt.booleanize! # 'on' => true
|
|
680
711
|
record_payload.deep_merge!(v_prompt)
|
|
681
712
|
end
|
|
713
|
+
# permissions
|
|
714
|
+
if rest_perms_config[:enabled]
|
|
715
|
+
if rest_perms_config[:version] == 2
|
|
716
|
+
perms = prompt_permissions_v2(options.deep_merge(rest_perms_config[:options] || {}), rest_perms_config[:excludes] || [])
|
|
717
|
+
else
|
|
718
|
+
perms = prompt_permissions(options.deep_merge(rest_perms_config[:options] || {}), rest_perms_config[:excludes] || [])
|
|
719
|
+
end
|
|
720
|
+
if !rest_perms_config[:name].nil?
|
|
721
|
+
perms.transform_keys! {|k| k == 'resourcePermissions' ? rest_perms_config[:name] : k}
|
|
722
|
+
end
|
|
723
|
+
record_payload.merge!(perms)
|
|
724
|
+
end
|
|
682
725
|
payload[rest_object_key] = record_payload
|
|
683
726
|
end
|
|
684
727
|
rest_interface.setopts(options)
|