morpheus-cli 5.5.3 → 5.5.3.2

Sign up to get free protection for your applications and to get access to all the features.
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