morpheus-cli 5.5.3 → 5.5.3.1

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 (28) 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/whitelabel_settings_command.rb +1 -1
  22. data/lib/morpheus/cli/mixins/provisioning_helper.rb +14 -12
  23. data/lib/morpheus/cli/mixins/rest_command.rb +5 -1
  24. data/lib/morpheus/cli/mixins/secondary_rest_command.rb +35 -12
  25. data/lib/morpheus/cli/version.rb +1 -1
  26. data/lib/morpheus/ext/string.rb +6 -4
  27. data/lib/morpheus/routes.rb +39 -7
  28. 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)
@@ -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)
@@ -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