morpheus-cli 5.5.3 → 5.5.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +1 -1
  3. data/lib/morpheus/api/api_client.rb +12 -0
  4. data/lib/morpheus/api/appliance_settings_interface.rb +15 -0
  5. data/lib/morpheus/api/cypher_interface.rb +1 -2
  6. data/lib/morpheus/api/guidance_settings_interface.rb +17 -0
  7. data/lib/morpheus/api/monitoring_settings_interface.rb +25 -0
  8. data/lib/morpheus/api/network_server_groups_interface.rb +7 -0
  9. data/lib/morpheus/cli/cli_command.rb +10 -1
  10. data/lib/morpheus/cli/commands/appliance_settings_command.rb +57 -2
  11. data/lib/morpheus/cli/commands/backup_settings_command.rb +1 -1
  12. data/lib/morpheus/cli/commands/catalog_item_types_command.rb +6 -1
  13. data/lib/morpheus/cli/commands/cypher_command.rb +3 -0
  14. data/lib/morpheus/cli/commands/guidance_command.rb +2 -2
  15. data/lib/morpheus/cli/commands/guidance_settings.rb +148 -0
  16. data/lib/morpheus/cli/commands/log_settings_command.rb +1 -1
  17. data/lib/morpheus/cli/commands/monitoring_settings.rb +228 -0
  18. data/lib/morpheus/cli/commands/network_server_groups_command.rb +222 -0
  19. data/lib/morpheus/cli/commands/provisioning_settings_command.rb +1 -1
  20. data/lib/morpheus/cli/commands/reports_command.rb +10 -0
  21. data/lib/morpheus/cli/commands/service_catalog_command.rb +36 -2
  22. data/lib/morpheus/cli/commands/whitelabel_settings_command.rb +1 -1
  23. data/lib/morpheus/cli/credentials.rb +2 -1
  24. data/lib/morpheus/cli/mixins/provisioning_helper.rb +14 -12
  25. data/lib/morpheus/cli/mixins/rest_command.rb +5 -1
  26. data/lib/morpheus/cli/mixins/secondary_rest_command.rb +35 -12
  27. data/lib/morpheus/cli/option_types.rb +14 -7
  28. data/lib/morpheus/cli/version.rb +1 -1
  29. data/lib/morpheus/cli.rb +13 -0
  30. data/lib/morpheus/ext/string.rb +6 -4
  31. data/lib/morpheus/routes.rb +39 -7
  32. data/test/test_case.rb +3 -0
  33. metadata +8 -2
@@ -0,0 +1,228 @@
1
+ require 'morpheus/cli/cli_command'
2
+
3
+ class Morpheus::Cli::MonitoringSettings
4
+ include Morpheus::Cli::CliCommand
5
+ include Morpheus::Cli::AccountsHelper
6
+
7
+ set_command_name :'monitor-settings'
8
+ set_command_description "View and manage monitoring settings"
9
+ register_subcommands :get, :update
10
+
11
+ def connect(opts)
12
+ @api_client = establish_remote_appliance_connection(opts)
13
+ @monitoring_settings_interface = @api_client.monitoring_settings
14
+ @options_interface = @api_client.options
15
+ end
16
+
17
+ def handle(args)
18
+ handle_subcommand(args)
19
+ end
20
+
21
+ def get(args)
22
+ params = {}
23
+ options = {}
24
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
25
+ opts.banner = subcommand_usage()
26
+ build_standard_get_options(opts, options)
27
+ opts.footer = "Get monitoring settings."
28
+ end
29
+ optparse.parse!(args)
30
+ connect(options)
31
+ verify_args!(args:args, optparse:optparse, count:0)
32
+ params.merge!(parse_query_options(options))
33
+ @monitoring_settings_interface.setopts(options)
34
+ if options[:dry_run]
35
+ print_dry_run @monitoring_settings_interface.dry.get(options)
36
+ return
37
+ end
38
+ json_response = @monitoring_settings_interface.get(options)
39
+ render_response(json_response, options, 'monitoringSettings') do
40
+ monitoring_settings = json_response['monitoringSettings']
41
+ service_now_settings = monitoring_settings['serviceNow']
42
+ new_relic_settings = monitoring_settings['newRelic']
43
+ print_h1 "Monitoring Settings"
44
+ print cyan
45
+ description_cols = {
46
+ "Auto Create Checks" => lambda {|it| format_boolean(it['autoManageChecks']) },
47
+ "Availability Time Frame" => lambda {|it| it['availabilityTimeFrame'] ? it['availabilityTimeFrame'].to_s + ' days' : '' },
48
+ "Availability Precision" => lambda {|it| it['availabilityPrecision'] ? it['availabilityPrecision'].to_s : '' },
49
+ "Default Check Interval" => lambda {|it| it['defaultCheckInterval'] ? it['defaultCheckInterval'].to_s + ' minutes' : '' },
50
+ }
51
+ print_description_list(description_cols, monitoring_settings, options)
52
+
53
+ print_h2 "ServiceNow Settings", options.merge(:border_style => :thin)
54
+ description_cols = {
55
+ "Enabled" => lambda {|it| format_boolean(it['enabled']) },
56
+ "Integration" => lambda {|it| it['integration'] ? it['integration']['name'] : '' },
57
+ "New Incident Action" => lambda {|it| format_service_now_action(it['newIncidentAction']) },
58
+ "Close Incident Action" => lambda {|it| format_service_now_action(it['closeIncidentAction']) },
59
+ "Info Mapping" => lambda {|it| format_service_now_mapping(it['infoMapping']) },
60
+ "Warning Mapping" => lambda {|it| format_service_now_mapping(it['warningMapping']) },
61
+ "Critical Mapping" => lambda {|it| format_service_now_mapping(it['criticalMapping']) },
62
+ }
63
+ print_description_list(description_cols, service_now_settings)
64
+
65
+ print_h2 "New Relic Settings", options.merge(:border_style => :thin)
66
+ description_cols = {
67
+ "Enabled" => lambda {|it| format_boolean(it['enabled']) },
68
+ "License Key" => lambda {|it| it['licenseKey'] },
69
+ }
70
+ print_description_list(description_cols, new_relic_settings, options)
71
+
72
+ print reset, "\n"
73
+ end
74
+ return 0, nil
75
+ end
76
+
77
+ def update(args)
78
+ params = {}
79
+ options = {}
80
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
81
+ opts.banner = opts.banner = subcommand_usage()
82
+ opts.on('--auto-create-checks [on|off]', String, "Auto Create Checks") do |val|
83
+ params['autoManageChecks'] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == '1' || val.to_s == ''
84
+ end
85
+ opts.on("--availability-time-frame DAYS", Integer, "Availability Time Frame. The number of days availability should be calculated for. Changes will not take effect until your checks have passed their check interval.") do |val|
86
+ params['availabilityTimeFrame'] = val.to_i
87
+ end
88
+ opts.on("--availability-precision DIGITS", Integer, "Availability Precision. The number of decimal places availability should be displayed in. Can be anywhere between 0 and 5.") do |val|
89
+ params['availabilityPrecision'] = val.to_i
90
+ end
91
+ opts.on("--default-check-interval MINUTES", Integer, "Default Check Interval. The default interval to use when creating new checks. Value is in minutes.") do |val|
92
+ params['defaultCheckInterval'] = val.to_i
93
+ end
94
+ opts.on('--service-now-enabled [on|off]', String, "ServiceNow: Enabled (on) or disabled (off)") do |val|
95
+ params['serviceNow'] ||= {}
96
+ params['serviceNow']['enabled'] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == '1' || val.to_s == ''
97
+ end
98
+ opts.on('--service-now-integration ID', String, "ServiceNow: Integration ID or Name") do |val|
99
+ params['serviceNow'] ||= {}
100
+ params['serviceNow']['integration'] = val # {'id' => val.to_i}
101
+ end
102
+ opts.on("--service-now-new-incident-action create|none", String, "ServiceNow: New Incident Action") do |val|
103
+ # allowed_values = 'create|none'.split('|') #get_service_now_actions().keys
104
+ # if !allowed_values.include?(val)
105
+ # raise ::OptionParser::InvalidOption.new("New Incident Action value '#{val}' is invalid.\nThe allowed values are: #{allowed_values.join(', ')}")
106
+ # end
107
+ params['serviceNow'] ||= {}
108
+ params['serviceNow']['newIncidentAction'] = val
109
+ end
110
+ opts.on("--service-now-close-incident-action close|activity|none", String, "ServiceNow: Close Incident Action") do |val|
111
+ # allowed_values = 'close|activity|none'.split('|') #get_service_now_mappings().keys
112
+ # if !allowed_values.include?(val)
113
+ # raise ::OptionParser::InvalidOption.new("Close Incident Action value '#{val}' is invalid.\nThe allowed values are: #{allowed_values.join(', ')}")
114
+ # end
115
+ params['serviceNow'] ||= {}
116
+ params['serviceNow']['closeIncidentAction'] = val
117
+ end
118
+ opts.on("--service-now-info-mapping low|medium|high", String, "ServiceNow: Info Mapping") do |val|
119
+ # allowed_values = 'low|medium|high'.split('|') # get_service_now_mappings().keys
120
+ # if !allowed_values.include?(val)
121
+ # raise ::OptionParser::InvalidOption.new("Info Mapping value '#{val}' is invalid.\nThe allowed values are: #{allowed_values.join(', ')}")
122
+ # end
123
+ params['serviceNow'] ||= {}
124
+ params['serviceNow']['infoMapping'] = val
125
+ end
126
+ opts.on("--service-now-warning-mapping low|medium|high", String, "ServiceNow: Warning Mapping") do |val|
127
+ # allowed_values = 'low|medium|high'.split('|') # get_service_now_mappings().keys
128
+ # if !allowed_values.include?(val)
129
+ # raise ::OptionParser::InvalidOption.new("Warning Info Mapping value '#{val}' is invalid.\nThe allowed values are: #{allowed_values.join(', ')}")
130
+ # end
131
+ params['serviceNow'] ||= {}
132
+ params['serviceNow']['warningMapping'] = val
133
+ end
134
+ opts.on("--service-now-critical-mapping low|medium|high", String, "ServiceNow: Critical Mapping") do |val|
135
+ # allowed_values = 'low|medium|high'.split('|') # get_service_now_mappings().keys
136
+ # if !allowed_values.include?(val)
137
+ # raise ::OptionParser::InvalidOption.new("Critical Info Mapping value '#{val}' is invalid.\nThe allowed values are: #{allowed_values.join(', ')}")
138
+ # end
139
+ params['serviceNow'] ||= {}
140
+ params['serviceNow']['criticalMapping'] = val
141
+ end
142
+ opts.on('--new-relic-enabled [on|off]', String, "New Relic: Enabled (on) or disabled (off)") do |val|
143
+ params['newRelic'] ||= {}
144
+ params['newRelic']['enabled'] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == '1' || val.to_s == ''
145
+ end
146
+ opts.on("--new-relic-license-key [VALUE]", String, "New Relic: License Key") do |val|
147
+ params['newRelic'] ||= {}
148
+ params['newRelic']['licenseKey'] = val
149
+ end
150
+ build_standard_update_options(opts, options)
151
+ opts.footer = "Update monitoring settings."
152
+ end
153
+ optparse.parse!(args)
154
+ connect(options)
155
+ verify_args!(args:args, optparse:optparse, count:0)
156
+ payload = parse_payload(options)
157
+ if !payload
158
+ payload = {}
159
+ payload.deep_merge!({object_key => parse_passed_options(options)}) # inject options passed with -O foo=bar
160
+ if params['serviceNow'] && params['serviceNow']['integration']
161
+ integration = find_by_name_or_id(:integration, params['serviceNow']['integration'])
162
+ if integration.nil?
163
+ exit 1 #return 1, "Integration not found by '#{options[:servicenow_integration]}'"
164
+ else
165
+ if integration['integrationType']['code'] != 'serviceNow'
166
+ raise_command_error "Integration '#{integration['id']}' must be a Service Now integration"
167
+ end
168
+ params['serviceNow'] ||= {}
169
+ params['serviceNow']['integration'] = {'id' => integration['id'].to_i}
170
+ end
171
+ end
172
+ payload.deep_merge!({object_key => params})
173
+ end
174
+ if payload[object_key].empty?
175
+ raise_command_error "Specify at least one option to update.\n#{optparse}"
176
+ end
177
+ @monitoring_settings_interface.setopts(options)
178
+ if options[:dry_run]
179
+ print_dry_run @monitoring_settings_interface.dry.update(payload)
180
+ return
181
+ end
182
+ json_response = @monitoring_settings_interface.update(payload)
183
+ exit_code, err = 0, nil
184
+ render_response(json_response, options, object_key) do
185
+ if json_response['success']
186
+ print_green_success "Updated monitoring settings"
187
+ get([] + (options[:remote] ? ["-r",options[:remote]] : []))
188
+ else
189
+ exit_code, err = 1, "Error updating monitoring settings: #{json_response['msg'] || json_response['errors']}"
190
+ print_rest_errors(json_response)
191
+ end
192
+ end
193
+ return exit_code, err
194
+ end
195
+
196
+ private
197
+
198
+ def get_service_now_actions()
199
+ {
200
+ 'create' => 'Create new incident in ServiceNow',
201
+ 'close' => 'Resolve Incident in ServiceNow',
202
+ 'activity' => 'Add Activity to Incident in ServiceNow',
203
+ 'none' => 'No action',
204
+ }
205
+ end
206
+
207
+ def format_service_now_action(action_value)
208
+ get_service_now_actions()[action_value].to_s
209
+ end
210
+
211
+ def get_service_now_mappings()
212
+ {
213
+ 'low' => 'Low',
214
+ 'medium' => 'Medium',
215
+ 'high' => 'High',
216
+ }
217
+ end
218
+
219
+ def format_service_now_mapping(mapping_value)
220
+ get_service_now_mappings()[mapping_value].to_s
221
+ end
222
+
223
+
224
+ def object_key
225
+ 'monitoringSettings'
226
+ end
227
+
228
+ end
@@ -0,0 +1,222 @@
1
+ require 'morpheus/cli/cli_command'
2
+
3
+ class Morpheus::Cli::NetworkServerGroups
4
+ include Morpheus::Cli::CliCommand
5
+ include Morpheus::Cli::WhoamiHelper
6
+ include Morpheus::Cli::RestCommand
7
+ include Morpheus::Cli::SecondaryRestCommand
8
+ include Morpheus::Cli::ProvisioningHelper
9
+
10
+ set_command_description "View and manage network server groups."
11
+ set_command_name :'network-server-groups'
12
+ register_subcommands :list, :get, :add, :update, :remove
13
+ register_interfaces :network_servers, :network_server_groups, :accounts
14
+ set_rest_perms_config({enabled:true, excludes:['groups', 'plans', 'visibility', 'resource'], context: 'permissions'})
15
+
16
+ protected
17
+
18
+ NSXT_CRITERIA_TYPES = ['Condition', 'NestedExpression'] unless defined? NSXT_CRITERIA_TYPES
19
+ NSXT_MEMBER_TYPES = ['Path', 'ExternalID'] unless defined? NSXT_MEMBER_TYPES
20
+ NSXT_IP_TYPES = ['IPAddress', 'MACAddress'] unless defined? NSXT_IP_TYPES
21
+ NSXT_AD_GROUP_TYPES = ['IdentityGroup'] unless defined? NSXT_AD_GROUP_TYPES
22
+
23
+ def network_server_group_list_key
24
+ 'groups'
25
+ end
26
+
27
+ def network_server_group_object_key
28
+ 'group'
29
+ end
30
+
31
+ def network_server_group_field_context
32
+ network_server_group_object_key
33
+ end
34
+
35
+ def load_option_types_for_network_server_group(record_type, parent_record)
36
+ parent_record['type']['groupOptionTypes']
37
+ end
38
+
39
+ def network_server_group_list_column_definitions(options)
40
+ if options[:parent_record]['type']['code'] == 'nsx-t'
41
+ members_lambda = lambda do |group|
42
+ members = []
43
+ {
44
+ 'Criteria' => NSXT_CRITERIA_TYPES,
45
+ 'Members' => NSXT_MEMBER_TYPES,
46
+ 'IPS / MACS' => NSXT_IP_TYPES,
47
+ 'AD Groups' => NSXT_AD_GROUP_TYPES
48
+ }.each do |label, types|
49
+ if (count = group['members'].select{|member| types.include?(member['type'])}.count) > 0
50
+ members << "#{count} #{label}"
51
+ end
52
+ end
53
+ members.join(', ')
54
+ end
55
+ else
56
+ members_lambda = lambda do |group|
57
+ group['members'].collect{|member| member['type']}.join(', ')
58
+ end
59
+ end
60
+
61
+ columns = {
62
+ 'ID' => 'id',
63
+ 'Name' => 'name',
64
+ 'Description' => {:label => 'Description', :max_width => 50, :display_method => lambda {|group| group['description']}},
65
+ 'Members' => members_lambda
66
+ }
67
+
68
+ if is_master_account
69
+ columns['Visibility'] = lambda {|it| it['visibility'].capitalize}
70
+ columns['Tenants'] = lambda do |it|
71
+ tenants = []
72
+ if it['permissions'] and it['permissions']['tenantPermissions']
73
+ tenants = @accounts_interface.list({:ids => it['permissions']['tenantPermissions']['accounts']})['accounts'].collect{|account| account['name']}
74
+ end
75
+ tenants.join(', ')
76
+ end
77
+ end
78
+ columns
79
+ end
80
+
81
+ def network_server_group_column_definitions(options)
82
+ if options[:parent_record]['type']['code'] == 'nsx-t'
83
+ tags_lambda = lambda{|group|
84
+ (group['tags'] || []).collect{|tag|
85
+ "#{tag['name']}#{(tag['value'] || '').length > 0 ? " (scope: #{tag['value']})" : ''}"
86
+ }.join(', ')
87
+ }
88
+ else
89
+ tags_lambda = lambda {|group| group['tags'] ? format_metadata(group['tags']) : '' }
90
+ end
91
+ columns = {
92
+ "ID" => 'id',
93
+ "Name" => 'name',
94
+ "Description" => 'description',
95
+ "Tags" => tags_lambda
96
+ }
97
+ columns
98
+ end
99
+
100
+ def network_server_group_add_prompt(record_payload, record_type, parent_record, options)
101
+ unless parent_record['type']['code'] != 'nsx-t' or options[:no_prompt]
102
+ nsxt_add_prompt(record_payload, record_type, parent_record, options)
103
+ end
104
+ end
105
+
106
+ def nsxt_add_prompt(record_payload, record_type, parent_record, options)
107
+ # criteria
108
+ criteria = []
109
+ while criteria.count < 5 && Morpheus::Cli::OptionTypes.confirm("Add#{criteria.count == 0 ? '': ' another'} criteria?", {:default => false})
110
+ if true #members.count == 0 or members.last['memberValue'] == 'OR' # Can't have nested follow AND conjunction
111
+ type = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'type', 'fieldLabel' => 'Criteria Type', 'type' => 'select', 'selectOptions' => ['Condition', 'Nested Expression'].map{|it| {'name' => it, 'value' => it.sub(' ', '')}}, 'required' => true, 'defaultValue' => 'Condition'}], options[:options])['type']
112
+ end
113
+ prompt_condition = lambda do
114
+ compare_type = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'memberType', 'fieldLabel' => 'Criteria Item', 'type' => 'select', 'selectOptions' => ['Virtual Machine', 'Segment Port', 'Segment', 'IP Set'].map{|it| {'name' => it, 'value' => it.sub(' ', '')}}, 'required' => true, 'defaultValue' => 'VirtualMachine'}], options[:options])['memberType']
115
+ compare_key = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'key', 'fieldLabel' => "#{compare_type} Field", 'type' => 'select', 'selectOptions' => (compare_type == 'VirtualMachine' ? ['Name', 'Tag', 'OS Name', 'Computer Name'] : ['Tag'] ).map{|it| {'name' => it, 'value' => it.sub(' ', '')}}, 'required' => true, 'defaultValue' => 'Tag'}], options[:options])['key']
116
+ compare_operator = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'operator', 'fieldLabel' => "#{compare_key} Operator", 'type' => 'select', 'selectOptions' => (compare_type == 'VirtualMachine' ? ['Equals', 'Contains', 'Starts With', 'Ends With'] : ['Equals']).map{|it| {'name' => it, 'value' => it.sub(' ', '').upcase}}, 'required' => true, 'defaultValue' => 'EQUALS'}], options[:options])['operator']
117
+ compare_value = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'value', 'type' => 'text', 'fieldLabel' => "#{compare_key} Value", 'required' => true, 'description' => 'Value to compare.'}], options[:options])['value']
118
+ compare_scope = nil
119
+ if compare_key == 'Tag'
120
+ compare_scope = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'scope', 'type' => 'text', 'fieldLabel' => "#{compare_key} Scope", 'required' => false}], options[:options], @api_client, {}, false, true)['scope']
121
+ end
122
+ compare_expr = {key: compare_key, operator: compare_operator, value: compare_value}
123
+ compare_expr.merge!({scope: compare_scope}) if compare_scope
124
+ {'type' => 'Condition', 'memberType' => compare_type, 'memberExpression' => JSON.generate(compare_expr)}
125
+ end
126
+
127
+ if criteria.count > 0
128
+ criteria.last['memberValue'] = 'OR'
129
+ end
130
+
131
+ if type == 'Condition'
132
+ prev_criteria = criteria.count > 0 ? criteria.last : nil
133
+ criteria << prompt_condition.call
134
+ if prev_criteria and prev_criteria['type'] != 'NestedExpression' and prev_criteria['memberType'] == criteria.last['memberType']
135
+ prev_criteria['memberValue'] = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'memberValue', 'fieldLabel' => 'And/Or', 'type' => 'select', 'selectOptions' => ['and', 'or'].map{|it| {'name' => it, 'value' => it.upcase}}, 'required' => true, 'defaultValue' => 'AND', 'description' => 'Conjunction to use between this condition and the previous condition'}], options[:options])['memberValue']
136
+ end
137
+ else
138
+ # just prompt for conditions w/
139
+ nested_members = [prompt_condition.call]
140
+ while nested_members.count < 5 && Morpheus::Cli::OptionTypes.confirm("Add another criteria to nested expression?", {:default => false})
141
+ nested_members.last['memberValue'] = 'AND'
142
+ nested_members << prompt_condition.call
143
+ end
144
+ criteria << {'type' => type, 'members' => nested_members}
145
+ end
146
+ end
147
+
148
+ members = []
149
+ while members.count < 500 && Morpheus::Cli::OptionTypes.confirm("Add#{members.count == 0 ? '': ' another'} member?", {:default => false})
150
+ member_type = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'memberType', 'fieldLabel' => 'Member Type', 'type' => 'select', 'selectOptions' => ['Group', 'Segment', 'Segment Port', 'Virtual Network Interface', 'Virtual Machine', 'Physical Server'].map{|it| {'name' => it, 'value' => it.gsub(' ', '')}}, 'required' => true, 'defaultValue' => 'Group'}], options[:options])['memberType']
151
+ member_value = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'memberValue', 'fieldLabel' => member_type, 'type' => 'select', 'optionSource' => 'nsxtGroupMembers', 'optionSourceType' => 'nsxt', 'required' => true}], options[:options], @api_client, {networkServerId: parent_record['id'], memberType: member_type}, false, true)['memberValue']
152
+ type = ['Group', 'Segment', 'SegmentPort'].include?(member_type) ? 'Path' : 'ExternalID'
153
+ members << {'type' => type, 'memberType' => member_type, 'memberValue' => member_value}
154
+ end
155
+
156
+ # ip/mac
157
+ ips = []
158
+ while members.count + ips.count < 500 && Morpheus::Cli::OptionTypes.confirm("Add#{ips.count == 0 ? '': ' another'} IP/MAC address?", {:default => false})
159
+ member_value = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'ipAddress', 'type' => 'text', 'fieldLabel' => "IP/MAC Address", 'required' => true, 'description' => 'Enter an IP or MAC address. x.x.x.x'}], options[:options])['ipAddress']
160
+ type = member_value.match(/[a-fA-F0-9]{2}(:[a-fA-F0-9]{2}){5}/) ? 'MACAddress' : 'IPAddress'
161
+ ips << {'type' => type, 'memberValue' => member_value}
162
+ end
163
+
164
+ # ad groups
165
+ ad_groups = []
166
+ while members.count + ips.count + ad_groups.count < 500 && Morpheus::Cli::OptionTypes.confirm("Add#{ad_groups.count == 0 ? '': ' another'} AD Group?", {:default => false})
167
+ member_value = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'identityGroup', 'type' => 'select', 'optionSource' => 'nsxtIdentityGroups', 'optionSourceType' => 'nsxt', 'required' => true, 'fieldLabel' => "AD Group"}], options[:options], @api_client, {networkServerId: parent_record['id'], memberType: member_type}, false, true)['identityGroup']
168
+ ad_groups << {'type' => 'IdentityGroup', 'memberValue' => member_value}
169
+ end
170
+
171
+ record_payload['members'] = criteria + members + ips + ad_groups
172
+ record_payload
173
+ end
174
+
175
+ def render_response_details_for_get(record, options)
176
+ if options[:parent_record]['type']['code'] == 'nsx-t'
177
+ members = record['members'].select{|member| NSXT_CRITERIA_TYPES.include?(member['type'])}
178
+ if members.count > 0
179
+ cond_criteria = lambda do |member|
180
+ expr = JSON.parse(member['memberExpression'])
181
+ "#{member['memberType']} #{expr['key']} #{expr['operator']} #{expr['value']}#{expr['scope'].nil? ? '' : " w/ #{expr['scope']} scope"}"
182
+ end
183
+ criteria_parts = []
184
+ members.each_with_index do |member, index|
185
+ if member['type'] == 'NestedExpression'
186
+ criteria_parts << "("
187
+ member['members'].each do |child_member|
188
+ criteria_parts << " #{cond_criteria.call(child_member)}"
189
+ criteria_parts << " #{child_member['memberValue']}"
190
+ end
191
+ criteria_parts.pop # remove last conjunction
192
+ criteria_parts << ")"
193
+ else
194
+ criteria_parts << cond_criteria.call(member)
195
+ criteria_parts << member['memberValue']
196
+ end
197
+ end
198
+ criteria_parts.pop if ['AND', 'OR'].include?(criteria_parts.last) # remove last conjunction
199
+ print_h2 "Criteria (#{members.count})", options
200
+ print "#{cyan}#{criteria_parts.join("\n")}\n"
201
+ end
202
+
203
+ members = record['members'].select{|member| NSXT_MEMBER_TYPES.include?(member['type'])}
204
+ if members.count > 0
205
+ print_h2 "Members (#{members.count})", options
206
+ print as_pretty_table(members, {'Type' => 'memberType', 'Path/ExternalID' => 'memberValue'}, options)
207
+ end
208
+
209
+ members = record['members'].select{|member| NSXT_IP_TYPES.include?(member['type'])}
210
+ if members.count > 0
211
+ print_h2 "IP/MAC Addresses (#{members.count})", options
212
+ print "#{cyan}#{members.collect{|member| "#{member['memberValue']}"}.join("\n")}\n"
213
+ end
214
+
215
+ members = record['members'].select{|member| NSXT_AD_GROUP_TYPES.include?(member['type'])}
216
+ if members.count > 0
217
+ print_h2 "AD Groups (#{members.count})", options
218
+ print "#{cyan}#{members.collect{|member| "#{member['memberValue']}"}.join("\n")}\n"
219
+ end
220
+ end
221
+ end
222
+ end
@@ -83,7 +83,7 @@ class Morpheus::Cli::ProvisioningSettingsCommand
83
83
  "Default Blueprint Type" => lambda {|it| it['defaultTemplateType'] ? it['defaultTemplateType']['name'].capitalize : 'Morpheus'}
84
84
  }
85
85
  print_description_list(description_cols, settings)
86
- print reset "\n"
86
+ print reset, "\n"
87
87
  return 0
88
88
  rescue RestClient::Exception => e
89
89
  print_rest_exception(e, options)
@@ -303,6 +303,16 @@ class Morpheus::Cli::ReportsCommand
303
303
 
304
304
  end
305
305
 
306
+ if payload['report']['startMonth'].size > 7 || payload['report']['endMonth'].size > 7
307
+ print_green_success "The CLI generates a query that will use only month and year. However, the API does support yyyy-mm-dd from a previous version of Morpheus.\nReplace startMonth/endMonth keys with startDate,endDate ie:"
308
+ payload['report'].delete('startMonth')
309
+ payload['report'].delete('endMonth')
310
+ payload['report']['startDate'] = 'yyyy-mm-dd'
311
+ payload['report']['endDate'] = 'yyyy-mm-dd'
312
+ print_dry_run @reports_interface.dry.create(payload)
313
+ return 0
314
+ end
315
+
306
316
  @reports_interface.setopts(options)
307
317
  if options[:dry_run]
308
318
  print_dry_run @reports_interface.dry.create(payload)
@@ -661,7 +661,8 @@ EOT
661
661
  it
662
662
  }
663
663
  if catalog_option_types && !catalog_option_types.empty?
664
- config_prompt = Morpheus::Cli::OptionTypes.prompt(catalog_option_types, options[:options], @api_client, {})['config']
664
+ api_params = construct_catalog_api_params(catalog_item_type)
665
+ config_prompt = Morpheus::Cli::OptionTypes.prompt(catalog_option_types, options[:options], @api_client, api_params, options[:no_prompt], false, false, true)['config']
665
666
  payload[add_item_object_key].deep_merge!({'config' => config_prompt})
666
667
  end
667
668
  if workflow_context
@@ -999,11 +1000,13 @@ EOT
999
1000
  catalog_option_types = catalog_item_type['optionTypes']
1000
1001
  # instead of config.customOptions just use config...
1001
1002
  catalog_option_types = catalog_option_types.collect {|it|
1003
+ it['fieldContext'] = nil
1002
1004
  it['fieldContext'] = 'config'
1003
1005
  it
1004
1006
  }
1005
1007
  if catalog_option_types && !catalog_option_types.empty?
1006
- config_prompt = Morpheus::Cli::OptionTypes.prompt(catalog_option_types, options[:options], @api_client, {})['config']
1008
+ api_params = construct_catalog_api_params(catalog_item_type)
1009
+ config_prompt = Morpheus::Cli::OptionTypes.prompt(catalog_option_types, options[:options], @api_client, api_params, options[:no_prompt], false, false, true)['config']
1007
1010
  item_payload.deep_merge!({'config' => config_prompt})
1008
1011
  end
1009
1012
 
@@ -1471,4 +1474,35 @@ EOT
1471
1474
  out + return_color
1472
1475
  end
1473
1476
 
1477
+
1478
+ def construct_catalog_api_params(record)
1479
+ catalog_item_type_id = record['id'].to_i
1480
+ api_params = {}
1481
+ api_params['catalogItemType'] ||= {}
1482
+ api_params['catalogItemType']['id'] = catalog_item_type_id
1483
+ # Determine instance.type value
1484
+ # the UI is injecting this parameter into the option source requests
1485
+ # it is needed to populate api option lists eg. optionTypeClouds
1486
+ # this should be fixed on the api side, so it automatically extracts this input from the config
1487
+ begin
1488
+ instance_type_code = nil
1489
+ catalog_item_type = find_by_id(:catalog_item_type, catalog_item_type_id.to_i)
1490
+ if catalog_item_type
1491
+ if catalog_item_type['type'] == 'instance'
1492
+ if catalog_item_type['config'] && catalog_item_type['config']['type']
1493
+ instance_type_code = catalog_item_type['config']['type']
1494
+ end
1495
+ end
1496
+ end
1497
+ if instance_type_code
1498
+ api_params['instance'] ||= {}
1499
+ api_params['instance']['type'] = instance_type_code
1500
+ end
1501
+ rescue ::RestClient::Exception => e
1502
+ # users may not have permission to this endpoint
1503
+ # puts "Failed to load catalog item type"
1504
+ end
1505
+ return api_params
1506
+ end
1507
+
1474
1508
  end
@@ -137,7 +137,7 @@ class Morpheus::Cli::WhitelabelSettingsCommand
137
137
  print cyan
138
138
  print options[:details] ? content : truncate_string(content, trunc_len), "\n"
139
139
  end
140
- print reset "\n"
140
+ print reset, "\n"
141
141
  return 0
142
142
  rescue RestClient::Exception => e
143
143
  print_rest_exception(e, options)
@@ -123,7 +123,8 @@ module Morpheus
123
123
  end
124
124
  if password.empty?
125
125
  # readline is still echoing secret with 'NUL:'' so just use $stdin on windows for now
126
- if Morpheus::Cli.windows?
126
+ # and some other environments? just use noecho unless running unit tests
127
+ if Morpheus::Cli.windows? || !Morpheus::Cli.testing?
127
128
  print "Password: #{required_blue_prompt} "
128
129
  # this should be my_terminal.stdin instead of STDIN and $stdin
129
130
  password = $stdin.noecho(&:gets).chomp!
@@ -1435,7 +1435,7 @@ module Morpheus::Cli::ProvisioningHelper
1435
1435
  'id' => current_volume['id'].to_i,
1436
1436
  'rootVolume' => false,
1437
1437
  'name' => current_volume['name'],
1438
- 'size' => current_volume['size'] > (plan_size || 0) ? current_volume['size'] : plan_size,
1438
+ 'size' => current_volume['size'] ? current_volume['size'] : (plan_size || 0),
1439
1439
  'sizeId' => nil,
1440
1440
  'storageType' => (current_volume['type'] || current_volume['storageType']),
1441
1441
  'datastoreId' => current_volume['datastoreId']
@@ -2003,9 +2003,10 @@ module Morpheus::Cli::ProvisioningHelper
2003
2003
  group_access = nil
2004
2004
  all_plans = nil
2005
2005
  plan_access = nil
2006
+ permissions = {}
2006
2007
 
2007
2008
  # Group Access
2008
- if !excludes.include?('groups')
2009
+ unless excludes.include?('groups')
2009
2010
  if !options[:groupAccessAll].nil?
2010
2011
  all_groups = options[:groupAccessAll]
2011
2012
  end
@@ -2055,7 +2056,7 @@ module Morpheus::Cli::ProvisioningHelper
2055
2056
  end
2056
2057
 
2057
2058
  # Plan Access
2058
- if !excludes.include?('plans')
2059
+ unless excludes.include?('plans')
2059
2060
  if !options[:planAccessAll].nil?
2060
2061
  all_plans = options[:planAccessAll]
2061
2062
  end
@@ -2101,13 +2102,14 @@ module Morpheus::Cli::ProvisioningHelper
2101
2102
  end
2102
2103
  end
2103
2104
 
2104
- resource_perms = {}
2105
- resource_perms['all'] = all_groups if !all_groups.nil?
2106
- resource_perms['sites'] = group_access if !group_access.nil?
2107
- resource_perms['allPlans'] = all_plans if !all_plans.nil?
2108
- resource_perms['plans'] = plan_access if !plan_access.nil?
2109
-
2110
- permissions = {'resourcePermissions' => resource_perms}
2105
+ unless excludes.include?('resource')
2106
+ resource_perms = {}
2107
+ resource_perms['all'] = all_groups if !all_groups.nil?
2108
+ resource_perms['sites'] = group_access if !group_access.nil?
2109
+ resource_perms['allPlans'] = all_plans if !all_plans.nil?
2110
+ resource_perms['plans'] = plan_access if !plan_access.nil?
2111
+ permissions['resourcePermissions'] = resource_perms
2112
+ end
2111
2113
 
2112
2114
  available_accounts = get_available_accounts() #.collect {|it| {'name' => it['name'], 'value' => it['id']}}
2113
2115
  accounts = []
@@ -2115,7 +2117,7 @@ module Morpheus::Cli::ProvisioningHelper
2115
2117
  # Prompts for multi tenant
2116
2118
  if available_accounts.count > 1
2117
2119
  visibility = options[:visibility]
2118
- if !excludes.include?('visibility')
2120
+ unless excludes.include?('visibility')
2119
2121
  if !visibility && !options[:no_prompt]
2120
2122
  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']
2121
2123
  end
@@ -2123,7 +2125,7 @@ module Morpheus::Cli::ProvisioningHelper
2123
2125
  end
2124
2126
 
2125
2127
  # Tenants
2126
- if !excludes.include?('tenants')
2128
+ unless excludes.include?('tenants')
2127
2129
  if !options[:tenants].nil?
2128
2130
  accounts = options[:tenants].collect {|id| id.to_i}
2129
2131
  elsif !options[:no_prompt]
@@ -720,9 +720,13 @@ EOT
720
720
  else
721
721
  perms = prompt_permissions(options.deep_merge(rest_perms_config[:options] || {}), rest_perms_config[:excludes] || [])
722
722
  end
723
- if !rest_perms_config[:name].nil?
723
+ unless rest_perms_config[:name].nil?
724
724
  perms.transform_keys! {|k| k == 'resourcePermissions' ? rest_perms_config[:name] : k}
725
725
  end
726
+ unless rest_perms_config[:context].nil?
727
+ perms_context = {}
728
+ perms_context[rest_perms_config[:context]] = perms
729
+ end
726
730
  record_payload.merge!(perms)
727
731
  end
728
732
  payload[rest_object_key] = record_payload