morpheus-cli 3.6.28 → 3.6.29

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 (40) hide show
  1. checksums.yaml +4 -4
  2. data/lib/morpheus/api/api_client.rb +16 -0
  3. data/lib/morpheus/api/cloud_folders_interface.rb +47 -0
  4. data/lib/morpheus/api/cloud_resource_pools_interface.rb +47 -0
  5. data/lib/morpheus/api/network_types_interface.rb +26 -0
  6. data/lib/morpheus/api/reports_interface.rb +77 -0
  7. data/lib/morpheus/api/security_group_rules_interface.rb +6 -0
  8. data/lib/morpheus/api/security_groups_interface.rb +21 -15
  9. data/lib/morpheus/cli.rb +3 -0
  10. data/lib/morpheus/cli/accounts.rb +1 -1
  11. data/lib/morpheus/cli/apps.rb +2 -2
  12. data/lib/morpheus/cli/archives_command.rb +18 -18
  13. data/lib/morpheus/cli/blueprints_command.rb +1 -1
  14. data/lib/morpheus/cli/boot_scripts_command.rb +6 -6
  15. data/lib/morpheus/cli/cli_command.rb +4 -0
  16. data/lib/morpheus/cli/cloud_datastores_command.rb +58 -20
  17. data/lib/morpheus/cli/cloud_folders_command.rb +463 -0
  18. data/lib/morpheus/cli/cloud_resource_pools_command.rb +707 -0
  19. data/lib/morpheus/cli/clouds.rb +2 -0
  20. data/lib/morpheus/cli/hosts.rb +33 -8
  21. data/lib/morpheus/cli/instances.rb +79 -54
  22. data/lib/morpheus/cli/library_option_lists_command.rb +1 -1
  23. data/lib/morpheus/cli/library_option_types_command.rb +1 -1
  24. data/lib/morpheus/cli/mixins/provisioning_helper.rb +11 -2
  25. data/lib/morpheus/cli/monitoring_contacts_command.rb +1 -1
  26. data/lib/morpheus/cli/monitoring_incidents_command.rb +1 -1
  27. data/lib/morpheus/cli/network_services_command.rb +7 -3
  28. data/lib/morpheus/cli/networks_command.rb +164 -63
  29. data/lib/morpheus/cli/option_types.rb +16 -15
  30. data/lib/morpheus/cli/policies_command.rb +76 -9
  31. data/lib/morpheus/cli/preseed_scripts_command.rb +2 -2
  32. data/lib/morpheus/cli/remote.rb +26 -28
  33. data/lib/morpheus/cli/reports_command.rb +594 -0
  34. data/lib/morpheus/cli/security_group_rules.rb +5 -1
  35. data/lib/morpheus/cli/security_groups.rb +882 -45
  36. data/lib/morpheus/cli/tasks.rb +158 -23
  37. data/lib/morpheus/cli/tenants_command.rb +1 -1
  38. data/lib/morpheus/cli/users.rb +1 -1
  39. data/lib/morpheus/cli/version.rb +1 -1
  40. metadata +9 -2
@@ -289,7 +289,7 @@ class Morpheus::Cli::BlueprintsCommand
289
289
  payload.deep_merge!(passed_options) unless passed_options.empty?
290
290
 
291
291
  if passed_options.empty?
292
- print_red_alert "Specify atleast one option to update"
292
+ print_red_alert "Specify at least one option to update"
293
293
  puts optparse
294
294
  return 1
295
295
  end
@@ -36,7 +36,7 @@ class Morpheus::Cli::BootScriptsCommand
36
36
  options = {}
37
37
  optparse = Morpheus::Cli::OptionParser.new do |opts|
38
38
  opts.banner = subcommand_usage()
39
- build_common_options(opts, options, [:list, :json, :dry_run])
39
+ build_common_options(opts, options, [:list, :json, :dry_run, :remote])
40
40
  end
41
41
  optparse.parse!(args)
42
42
  connect(options)
@@ -94,7 +94,7 @@ class Morpheus::Cli::BootScriptsCommand
94
94
  options = {}
95
95
  optparse = Morpheus::Cli::OptionParser.new do |opts|
96
96
  opts.banner = subcommand_usage("[boot-script]")
97
- build_common_options(opts, options, [:json, :dry_run])
97
+ build_common_options(opts, options, [:json, :dry_run, :remote])
98
98
  end
99
99
  optparse.parse!(args)
100
100
  if args.count < 1
@@ -150,7 +150,7 @@ class Morpheus::Cli::BootScriptsCommand
150
150
  optparse = Morpheus::Cli::OptionParser.new do |opts|
151
151
  opts.banner = subcommand_usage("[fileName]")
152
152
  build_option_type_options(opts, options, add_boot_script_option_types(false))
153
- build_common_options(opts, options, [:options, :json, :dry_run, :quiet])
153
+ build_common_options(opts, options, [:options, :json, :dry_run, :quiet, :remote])
154
154
  end
155
155
  optparse.parse!(args)
156
156
  connect(options)
@@ -210,7 +210,7 @@ class Morpheus::Cli::BootScriptsCommand
210
210
  optparse = Morpheus::Cli::OptionParser.new do |opts|
211
211
  opts.banner = subcommand_usage("[boot-script] [options]")
212
212
  build_option_type_options(opts, options, update_boot_script_option_types(false))
213
- build_common_options(opts, options, [:options, :json, :dry_run])
213
+ build_common_options(opts, options, [:options, :json, :dry_run, :remote])
214
214
  end
215
215
  optparse.parse!(args)
216
216
  if args.count < 1
@@ -230,7 +230,7 @@ class Morpheus::Cli::BootScriptsCommand
230
230
  #puts "parsed params is : #{params.inspect}"
231
231
  params = params.select {|k,v| params[k].to_s != "" }
232
232
  if params.empty?
233
- print_red_alert "Specify atleast one option to update"
233
+ print_red_alert "Specify at least one option to update"
234
234
  puts optparse
235
235
  return 1
236
236
  end
@@ -279,7 +279,7 @@ class Morpheus::Cli::BootScriptsCommand
279
279
  options = {}
280
280
  optparse = Morpheus::Cli::OptionParser.new do |opts|
281
281
  opts.banner = subcommand_usage("[boot-script]")
282
- build_common_options(opts, options, [:account, :auto_confirm, :json, :dry_run])
282
+ build_common_options(opts, options, [:account, :auto_confirm, :json, :dry_run, :remote])
283
283
  end
284
284
  optparse.parse!(args)
285
285
 
@@ -197,6 +197,10 @@ module Morpheus
197
197
  # unless command_name == "shell"
198
198
  # option_keys << :quiet unless option_keys.include?(:quiet)
199
199
  # end
200
+
201
+ # ensure commands can always access options[:options], until we can deprecate it...
202
+ options[:options] ||= {}
203
+
200
204
  while (option_key = option_keys.shift) do
201
205
  case option_key.to_sym
202
206
 
@@ -33,15 +33,23 @@ class Morpheus::Cli::CloudDatastoresCommand
33
33
  options = {}
34
34
  params = {}
35
35
  optparse = Morpheus::Cli::OptionParser.new do |opts|
36
- opts.banner = subcommand_usage("-c [cloud]")
36
+ opts.banner = subcommand_usage("[cloud]")
37
37
  opts.on( '-c', '--cloud CLOUD', "Cloud Name or ID" ) do |val|
38
38
  cloud_id = val
39
39
  end
40
+ opts.add_hidden_option('-c') # prefer args[0] for [cloud]
40
41
  build_common_options(opts, options, [:list, :json, :yaml, :csv, :fields, :json, :dry_run, :remote])
41
42
  opts.footer = "List datastores for a cloud." + "\n" +
42
- "-c [cloud] is required. This is the name or id of the cloud."
43
+ "[cloud] is required. This is the name or id of the cloud."
43
44
  end
44
45
  optparse.parse!(args)
46
+ if args.count == 1
47
+ cloud_id = args[0]
48
+ elsif args.count == 0 && cloud_id
49
+ # support -c
50
+ else
51
+ raise_command_error "wrong number of arguments, expected 1 and got (#{args.count}) #{args.join(' ')}\n#{optparse}"
52
+ end
45
53
  connect(options)
46
54
  begin
47
55
  # load cloud
@@ -111,22 +119,27 @@ class Morpheus::Cli::CloudDatastoresCommand
111
119
 
112
120
  def get(args)
113
121
  cloud_id = nil
122
+ datastore_id = nil
114
123
  options = {}
115
124
  optparse = Morpheus::Cli::OptionParser.new do |opts|
116
- opts.banner = subcommand_usage("[datastore]")
125
+ opts.banner = subcommand_usage("[cloud] [datastore]")
117
126
  opts.on( '-c', '--cloud CLOUD', "Cloud Name or ID" ) do |val|
118
127
  cloud_id = val
119
128
  end
129
+ opts.add_hidden_option('-c') # prefer args[0] for [cloud]
120
130
  build_common_options(opts, options, [:json, :yaml, :csv, :fields, :dry_run, :remote])
121
131
  opts.footer = "Get details about a datastore." + "\n" +
122
- "[datastore] is required. This is the name or id of a datastore." + "\n" +
123
- "-c [cloud] is required. This is the name or id of the cloud."
132
+ "[cloud] is required. This is the name or id of the cloud." + "\n" +
133
+ "[datastore] is required. This is the name or id of a datastore."
124
134
  end
125
135
  optparse.parse!(args)
126
- if args.count != 1
127
- print_error Morpheus::Terminal.angry_prompt
128
- puts_error "#{command_name} missing argument: [datastore]\n#{optparse}"
129
- return 1
136
+ if args.count == 2
137
+ cloud_id = args[0]
138
+ datastore_id = args[1]
139
+ elsif args.count == 1 && cloud_id
140
+ datastore_id = args[0]
141
+ else
142
+ raise_command_error "wrong number of arguments, expected 2 and got (#{args.count}) #{args.join(' ')}\n#{optparse}"
130
143
  end
131
144
  connect(options)
132
145
  begin
@@ -139,14 +152,14 @@ class Morpheus::Cli::CloudDatastoresCommand
139
152
  return 1 if cloud.nil?
140
153
  @cloud_datastores_interface.setopts(options)
141
154
  if options[:dry_run]
142
- if args[0].to_s =~ /\A\d{1,}\Z/
143
- print_dry_run @cloud_datastores_interface.dry.get(cloud['id'], args[0].to_i)
155
+ if datastore_id.to_s =~ /\A\d{1,}\Z/
156
+ print_dry_run @cloud_datastores_interface.dry.get(cloud['id'], datastore_id.to_i)
144
157
  else
145
- print_dry_run @cloud_datastores_interface.dry.list(cloud['id'], {name:args[0]})
158
+ print_dry_run @cloud_datastores_interface.dry.list(cloud['id'], {name:datastore_id})
146
159
  end
147
160
  return
148
161
  end
149
- datastore = find_datastore_by_name_or_id(cloud['id'], args[0])
162
+ datastore = find_datastore_by_name_or_id(cloud['id'], datastore_id)
150
163
  return 1 if datastore.nil?
151
164
  json_response = {'datastore' => datastore} # skip redundant request
152
165
  # json_response = @datastores_interface.get(datastore['id'])
@@ -200,6 +213,21 @@ class Morpheus::Cli::CloudDatastoresCommand
200
213
  print as_pretty_table(rows, columns)
201
214
  end
202
215
 
216
+ if datastore['tenants'].nil? || datastore['tenants'].nil?
217
+ #print "\n", "No tenant permissions found", "\n"
218
+ else
219
+ print_h2 "Tenant Permissions"
220
+ rows = []
221
+ rows = datastore['tenants'] || []
222
+ tenant_columns = {
223
+ "TENANT" => 'name',
224
+ #"DEFAULT" => lambda {|it| format_boolean(it['defaultTarget']) },
225
+ "IMAGE TARGET" => lambda {|it| format_boolean(it['defaultStore']) }
226
+ }
227
+ print cyan
228
+ print as_pretty_table(rows, tenant_columns)
229
+ end
230
+
203
231
  print reset,"\n"
204
232
  return 0
205
233
  rescue RestClient::Exception => e
@@ -210,16 +238,18 @@ class Morpheus::Cli::CloudDatastoresCommand
210
238
 
211
239
  def update(args)
212
240
  options = {}
241
+ datastore_id = nil
213
242
  cloud_id = nil
214
243
  tenants = nil
215
244
  group_access_all = nil
216
245
  group_access_list = nil
217
246
  group_defaults_list = nil
218
247
  optparse = Morpheus::Cli::OptionParser.new do |opts|
219
- opts.banner = subcommand_usage("[datastore] -c [cloud] [options]")
248
+ opts.banner = subcommand_usage("[cloud] [datastore] [options]")
220
249
  opts.on( '-c', '--cloud CLOUD', "Cloud Name or ID" ) do |val|
221
250
  cloud_id = val
222
251
  end
252
+ opts.add_hidden_option('-c') # prefer args[0] for [cloud]
223
253
  opts.on('--group-access-all [on|off]', String, "Toggle Access for all groups.") do |val|
224
254
  group_access_all = val.to_s == 'on' || val.to_s == 'true'
225
255
  end
@@ -252,13 +282,17 @@ class Morpheus::Cli::CloudDatastoresCommand
252
282
  end
253
283
  build_common_options(opts, options, [:options, :payload, :json, :dry_run, :remote])
254
284
  opts.footer = "Update a datastore." + "\n" +
255
- "[datastore] is required. This is the id of a datstore."
285
+ "[cloud] is required. This is the name or id of the cloud." + "\n" +
286
+ "[datastore] is required. This is the name or id of a datastore."
256
287
  end
257
288
  optparse.parse!(args)
258
- if args.count != 1
259
- print_error Morpheus::Terminal.angry_prompt
260
- puts_error "wrong number of arguments, expected 1 and got #{args.count}\n#{optparse}"
261
- return 1
289
+ if args.count == 2
290
+ cloud_id = args[0]
291
+ datastore_id = args[1]
292
+ elsif args.count == 1 && cloud_id
293
+ datastore_id = args[0]
294
+ else
295
+ raise_command_error "wrong number of arguments, expected 2 and got (#{args.count}) #{args.join(' ')}\n#{optparse}"
262
296
  end
263
297
  connect(options)
264
298
 
@@ -271,7 +305,7 @@ class Morpheus::Cli::CloudDatastoresCommand
271
305
  cloud = find_cloud_by_name_or_id(cloud_id)
272
306
  return 1 if cloud.nil?
273
307
 
274
- datastore = find_datastore_by_name_or_id(cloud['id'], args[0])
308
+ datastore = find_datastore_by_name_or_id(cloud['id'], datastore_id)
275
309
  return 1 if datastore.nil?
276
310
 
277
311
  # merge -O options into normally parsed options
@@ -324,6 +358,10 @@ class Morpheus::Cli::CloudDatastoresCommand
324
358
  payload['datastore']['visibility'] = options['visibility']
325
359
  end
326
360
 
361
+ if payload['datastore'].empty? && payload['resourcePermissions'].nil? && payload['tenantPermissions'].nil?
362
+ raise_command_error "Specify at least one option to update.\n#{optparse}"
363
+ end
364
+
327
365
  end
328
366
  @cloud_datastores_interface.setopts(options)
329
367
  if options[:dry_run]
@@ -0,0 +1,463 @@
1
+ require 'rest_client'
2
+ require 'optparse'
3
+ require 'filesize'
4
+ require 'morpheus/cli/cli_command'
5
+ require 'morpheus/cli/mixins/infrastructure_helper'
6
+
7
+ class Morpheus::Cli::CloudFoldersCommand
8
+ include Morpheus::Cli::CliCommand
9
+ include Morpheus::Cli::InfrastructureHelper
10
+
11
+ #set_command_name :'cloud-folders'
12
+ set_command_name :'resource-folders'
13
+
14
+ register_subcommands :list, :get, :update
15
+
16
+ def initialize()
17
+ # @appliance_name, @appliance_url = Morpheus::Cli::Remote.active_appliance
18
+ end
19
+
20
+ def connect(opts)
21
+ @api_client = establish_remote_appliance_connection(opts)
22
+ @cloud_folders_interface = @api_client.cloud_folders
23
+ @clouds_interface = @api_client.clouds
24
+ @options_interface = @api_client.options
25
+ end
26
+
27
+ def handle(args)
28
+ handle_subcommand(args)
29
+ end
30
+
31
+ def list(args)
32
+ cloud_id = nil
33
+ options = {}
34
+ params = {}
35
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
36
+ opts.banner = subcommand_usage("[cloud]")
37
+ opts.on( '-c', '--cloud CLOUD', "Cloud Name or ID" ) do |val|
38
+ cloud_id = val
39
+ end
40
+ opts.add_hidden_option('-c') # prefer args[0] for [cloud]
41
+ build_common_options(opts, options, [:list, :json, :yaml, :csv, :fields, :dry_run, :remote])
42
+ opts.footer = "List resource folders for a cloud." + "\n" +
43
+ "[cloud] is required. This is the name or id of the cloud."
44
+ end
45
+ optparse.parse!(args)
46
+ if args.count == 1
47
+ cloud_id = args[0]
48
+ elsif args.count == 0 && cloud_id
49
+ # support -c
50
+ else
51
+ raise_command_error "wrong number of arguments, expected 2 and got (#{args.count}) #{args.join(' ')}\n#{optparse}"
52
+ end
53
+ connect(options)
54
+ begin
55
+ # load cloud
56
+ if cloud_id.nil?
57
+ puts_error "#{Morpheus::Terminal.angry_prompt}missing required option: [cloud]\n#{optparse}"
58
+ return 1
59
+ end
60
+ cloud = find_cloud_by_name_or_id(cloud_id)
61
+ return 1 if cloud.nil?
62
+
63
+ params.merge!(parse_list_options(options))
64
+ @cloud_folders_interface.setopts(options)
65
+ if options[:dry_run]
66
+ print_dry_run @cloud_folders_interface.dry.list(cloud['id'], params)
67
+ return
68
+ end
69
+ json_response = @cloud_folders_interface.list(cloud['id'], params)
70
+ folders = json_response["folders"]
71
+ if options[:json]
72
+ puts as_json(json_response, options, "folders")
73
+ return 0
74
+ elsif options[:yaml]
75
+ puts as_yaml(json_response, options, "folders")
76
+ return 0
77
+ elsif options[:csv]
78
+ puts records_as_csv(folders, options)
79
+ return 0
80
+ end
81
+ title = "Morpheus Resource Folders - Cloud: #{cloud['name']}"
82
+ subtitles = []
83
+ subtitles += parse_list_subtitles(options)
84
+ print_h1 title, subtitles
85
+ if folders.empty?
86
+ print cyan,"No resource folders found.",reset,"\n"
87
+ else
88
+ rows = folders.collect {|folder|
89
+ formatted_name = (folder['depth'] && folder['depth'] > 0) ? ((' ' * folder['depth'].to_i) + folder['name'].to_s) : folder['name'].to_s
90
+ row = {
91
+ id: folder['id'],
92
+ # name: folder['name'],
93
+ name: formatted_name,
94
+ type: folder['type'].to_s.capitalize,
95
+ description: folder['description'],
96
+ active: format_boolean(folder['active']),
97
+ visibility: folder['visibility'].to_s.capitalize,
98
+ default: format_boolean(folder['defaultFolder']),
99
+ imageTarget: format_boolean(folder['defaultStore']),
100
+ tenants: folder['tenants'] ? folder['tenants'].collect {|it| it['name'] }.uniq.join(', ') : ''
101
+ # owner: folder['owner'] ? folder['owner']['name'] : ''
102
+ }
103
+ row
104
+ }
105
+ columns = [:id, :name, :active, :default, :visibility, :tenants]
106
+ if options[:include_fields]
107
+ columns = options[:include_fields]
108
+ end
109
+ print cyan
110
+ print as_pretty_table(rows, columns, options)
111
+ print reset
112
+ print_results_pagination(json_response)
113
+ end
114
+ print reset,"\n"
115
+ return 0
116
+ rescue RestClient::Exception => e
117
+ print_rest_exception(e, options)
118
+ exit 1
119
+ end
120
+ end
121
+
122
+ def get(args)
123
+ folder_id = nil
124
+ cloud_id = nil
125
+ options = {}
126
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
127
+ opts.banner = subcommand_usage("[cloud] [folder]")
128
+ opts.on( '-c', '--cloud CLOUD', "Cloud Name or ID" ) do |val|
129
+ cloud_id = val
130
+ end
131
+ opts.add_hidden_option('-c') # prefer args[0] for [cloud]
132
+ build_common_options(opts, options, [:json, :yaml, :csv, :fields, :dry_run, :remote])
133
+ opts.footer = "Get details about a resource folder." + "\n" +
134
+ "[cloud] is required. This is the name or id of the cloud." + "\n"
135
+ "[folder] is required. This is the name or id of a resource folder."
136
+ end
137
+ optparse.parse!(args)
138
+ if args.count == 2
139
+ cloud_id = args[0]
140
+ folder_id = args[1]
141
+ elsif args.count == 1 && cloud_id
142
+ folder_id = args[0]
143
+ else
144
+ raise_command_error "wrong number of arguments, expected 2 and got (#{args.count}) #{args.join(' ')}\n#{optparse}"
145
+ end
146
+ connect(options)
147
+ begin
148
+ # load cloud
149
+ if cloud_id.nil?
150
+ puts_error "#{Morpheus::Terminal.angry_prompt}missing required option: [cloud]\n#{optparse}"
151
+ return 1
152
+ end
153
+ cloud = find_cloud_by_name_or_id(cloud_id)
154
+ return 1 if cloud.nil?
155
+ @cloud_folders_interface.setopts(options)
156
+ if options[:dry_run]
157
+ if args[0].to_s =~ /\A\d{1,}\Z/
158
+ print_dry_run @cloud_folders_interface.dry.get(cloud['id'], folder_id.to_i)
159
+ else
160
+ print_dry_run @cloud_folders_interface.dry.list(cloud['id'], {name:folder_id})
161
+ end
162
+ return
163
+ end
164
+ folder = find_folder_by_name_or_id(cloud['id'], folder_id)
165
+ return 1 if folder.nil?
166
+ json_response = {'folder' => folder} # skip redundant request
167
+ # json_response = @folders_interface.get(folder['id'])
168
+ folder = json_response['folder']
169
+ if options[:json]
170
+ puts as_json(json_response, options, "folder")
171
+ return 0
172
+ elsif options[:yaml]
173
+ puts as_yaml(json_response, options, "folder")
174
+ return 0
175
+ elsif options[:csv]
176
+ puts records_as_csv([folder], options)
177
+ return 0
178
+ end
179
+ print_h1 "Resource Folder Details"
180
+ print cyan
181
+ description_cols = {
182
+ "ID" => 'id',
183
+ "Name" => 'name',
184
+ "Description" => 'description',
185
+ #"Type" => lambda {|it| it['type'].to_s.capitalize },
186
+ "Cloud" => lambda {|it| it['zone'] ? it['zone']['name'] : '' },
187
+ "Active" => lambda {|it| format_boolean(it['active']) },
188
+ "Default" => lambda {|it| format_boolean(it['defaultPool']) },
189
+ "Visibility" => lambda {|it| it['visibility'].to_s.capitalize },
190
+ #"Tenants" => lambda {|it| it['tenants'] ? it['tenants'].collect {|it| it['name'] }.uniq.join(', ') : '' }
191
+ # "Owner" => lambda {|it| it['owner'] ? it['owner']['name'] : '' },
192
+ }
193
+ print_description_list(description_cols, folder)
194
+
195
+ if folder['resourcePermission'].nil?
196
+ #print "\n", "No group access found", "\n"
197
+ else
198
+ print_h2 "Group Access"
199
+ rows = []
200
+ if folder['resourcePermission']['all']
201
+ rows.push({"name" => 'All'})
202
+ end
203
+ if folder['resourcePermission']['sites']
204
+ folder['resourcePermission']['sites'].each do |site|
205
+ rows.push(site)
206
+ end
207
+ end
208
+ group_columns = {
209
+ "GROUP" => 'name',
210
+ "DEFAULT" => lambda {|it| it['default'].nil? ? '' : format_boolean(it['default']) }
211
+ }
212
+ print cyan
213
+ print as_pretty_table(rows, group_columns)
214
+ end
215
+
216
+ if folder['resourcePermission'] && folder['resourcePermission']['plans'] && folder['resourcePermission']['plans'].size > 0
217
+ print_h2 "Service Plan Access"
218
+ rows = []
219
+ if folder['resourcePermission']['allPlans']
220
+ rows.push({"name" => 'All'})
221
+ end
222
+ if folder['resourcePermission']['plans']
223
+ folder['resourcePermission']['plans'].each do |plan|
224
+ rows.push(plan)
225
+ end
226
+ end
227
+ # rows = rows.collect do |site|
228
+ # {plan: site['name'], default: site['default'] ? 'Yes' : ''}
229
+ # #{group: site['name']}
230
+ # end
231
+ plan_columns = {
232
+ "PLAN" => 'name',
233
+ "DEFAULT" => lambda {|it| it['default'].nil? ? '' : format_boolean(it['default']) }
234
+ }
235
+ print cyan
236
+ print as_pretty_table(rows, plan_columns)
237
+ end
238
+
239
+ if folder['tenants'].nil? || folder['tenants'].empty?
240
+ #print "\n", "No tenant permissions found", "\n"
241
+ else
242
+ print_h2 "Tenant Permissions"
243
+ rows = []
244
+ rows = folder['tenants'] || []
245
+ tenant_columns = {
246
+ "TENANT" => 'name',
247
+ "DEFAULT" => lambda {|it| format_boolean(it['defaultTarget']) },
248
+ "IMAGE TARGET" => lambda {|it| format_boolean(it['defaultStore']) }
249
+ }
250
+ print cyan
251
+ print as_pretty_table(rows, tenant_columns)
252
+ end
253
+
254
+ print reset,"\n"
255
+ return 0
256
+ rescue RestClient::Exception => e
257
+ print_rest_exception(e, options)
258
+ return 1
259
+ end
260
+ end
261
+
262
+ def update(args)
263
+ options = {}
264
+ cloud_id = nil
265
+ folder_id = nil
266
+ tenants = nil
267
+ group_access_all = nil
268
+ group_access_list = nil
269
+ group_defaults_list = nil
270
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
271
+ opts.banner = subcommand_usage("[cloud] [folder] [options]")
272
+ opts.on( '-c', '--cloud CLOUD', "Cloud Name or ID" ) do |val|
273
+ cloud_id = val
274
+ end
275
+ opts.add_hidden_option('-c') # prefer args[0] for [cloud]
276
+ opts.on('--group-access-all [on|off]', String, "Toggle Access for all groups.") do |val|
277
+ group_access_all = val.to_s == 'on' || val.to_s == 'true' || val.to_s == ''
278
+ end
279
+ opts.on('--group-access LIST', Array, "Group Access, comma separated list of group IDs.") do |list|
280
+ if list.size == 1 && list[0] == 'null' # hacky way to clear it
281
+ group_access_list = []
282
+ else
283
+ group_access_list = list.collect {|it| it.to_s.strip.empty? ? nil : it.to_s.strip }.compact.uniq
284
+ end
285
+ end
286
+ # opts.on('--group-defaults LIST', Array, "Group Default Selection, comma separated list of group IDs") do |list|
287
+ # if list.size == 1 && list[0] == 'null' # hacky way to clear it
288
+ # group_defaults_list = []
289
+ # else
290
+ # group_defaults_list = list.collect {|it| it.to_s.strip.empty? ? nil : it.to_s.strip }.compact.uniq
291
+ # end
292
+ # end
293
+ opts.on('--tenants LIST', Array, "Tenant Access, comma separated list of account IDs") do |list|
294
+ if list.size == 1 && list[0] == 'null' # hacky way to clear it
295
+ options['tenants'] = []
296
+ else
297
+ options['tenants'] = list.collect {|it| it.to_s.strip.empty? ? nil : it.to_s.strip }.compact.uniq
298
+ end
299
+ end
300
+ opts.on('--visibility [private|public]', String, "Visibility") do |val|
301
+ options['visibility'] = val
302
+ end
303
+ opts.on('--active [on|off]', String, "Can be used to disable a resource folder") do |val|
304
+ options['active'] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == ''
305
+ end
306
+ build_common_options(opts, options, [:options, :payload, :json, :dry_run, :remote])
307
+ opts.footer = "Update a resource folder." + "\n" +
308
+ "[cloud] is required. This is the name or id of the cloud." + "\n"
309
+ "[folder] is required. This is the id of a folder."
310
+ end
311
+ optparse.parse!(args)
312
+ if args.count == 2
313
+ cloud_id = args[0]
314
+ folder_id = args[1]
315
+ elsif args.count == 1 && cloud_id
316
+ folder_id = args[0]
317
+ else
318
+ raise_command_error "wrong number of arguments, expected 2 and got (#{args.count}) #{args.join(' ')}\n#{optparse}"
319
+ end
320
+
321
+ connect(options)
322
+
323
+ begin
324
+ # load cloud
325
+ if cloud_id.nil?
326
+ puts_error "#{Morpheus::Terminal.angry_prompt}missing required option: [cloud]\n#{optparse}"
327
+ return 1
328
+ end
329
+ cloud = find_cloud_by_name_or_id(cloud_id)
330
+ return 1 if cloud.nil?
331
+
332
+ folder = find_folder_by_name_or_id(cloud['id'], folder_id)
333
+ return 1 if folder.nil?
334
+
335
+ # merge -O options into normally parsed options
336
+ options.deep_merge!(options[:options].reject {|k,v| k.is_a?(Symbol) }) if options[:options]
337
+
338
+ # construct payload
339
+ payload = nil
340
+ if options[:payload]
341
+ payload = options[:payload]
342
+ else
343
+ # prompt for resource folder options
344
+ payload = {
345
+ 'folder' => {
346
+ }
347
+ }
348
+
349
+ # allow arbitrary -O options
350
+ payload['folder'].deep_merge!(options[:options].reject {|k,v| k.is_a?(Symbol) }) if options[:options]
351
+
352
+
353
+ # Group Access
354
+ if group_access_all != nil
355
+ payload['resourcePermissions'] ||= {}
356
+ payload['resourcePermissions']['all'] = group_access_all
357
+ end
358
+ if group_access_list != nil
359
+ payload['resourcePermissions'] ||= {}
360
+ payload['resourcePermissions']['sites'] = group_access_list.collect do |site_id|
361
+ site = {"id" => site_id.to_i}
362
+ if group_defaults_list && group_defaults_list.include?(site_id)
363
+ site["default"] = true
364
+ end
365
+ site
366
+ end
367
+ end
368
+
369
+ # Tenants
370
+ if options['tenants']
371
+ payload['tenantPermissions'] = {}
372
+ payload['tenantPermissions']['accounts'] = options['tenants']
373
+ end
374
+
375
+ # Active
376
+ if options['active'] != nil
377
+ payload['folder']['active'] = options['active']
378
+ end
379
+
380
+ # Visibility
381
+ if options['visibility'] != nil
382
+ payload['folder']['visibility'] = options['visibility']
383
+ end
384
+
385
+ if payload['folder'].empty? && payload['resourcePermissions'].nil? && payload['tenantPermissions'].nil?
386
+ raise_command_error "Specify at least one option to update.\n#{optparse}"
387
+ end
388
+
389
+ end
390
+ @cloud_folders_interface.setopts(options)
391
+ if options[:dry_run]
392
+ print_dry_run @cloud_folders_interface.dry.update(cloud['id'], folder["id"], payload)
393
+ return
394
+ end
395
+ json_response = @cloud_folders_interface.update(cloud['id'], folder["id"], payload)
396
+ if options[:json]
397
+ puts as_json(json_response)
398
+ else
399
+ folder = json_response['folder']
400
+ print_green_success "Updated resource folder #{folder['name']}"
401
+ get([folder['id'], "-c", cloud['id'].to_s]) # argh, to_s needed on option values..
402
+ end
403
+ return 0
404
+ rescue RestClient::Exception => e
405
+ print_rest_exception(e, options)
406
+ return 1
407
+ end
408
+ end
409
+
410
+ private
411
+
412
+
413
+ def find_folder_by_name_or_id(cloud_id, val)
414
+ if val.to_s =~ /\A\d{1,}\Z/
415
+ return find_folder_by_id(cloud_id, val)
416
+ else
417
+ return find_folder_by_name(cloud_id, val)
418
+ end
419
+ end
420
+
421
+ def find_folder_by_id(cloud_id, id)
422
+ begin
423
+ json_response = @cloud_folders_interface.get(cloud_id, id.to_i)
424
+ return json_response['folder']
425
+ rescue RestClient::Exception => e
426
+ if e.response && e.response.code == 404
427
+ print_red_alert "Resource Folder not found by id #{id}"
428
+ return nil
429
+ else
430
+ raise e
431
+ end
432
+ end
433
+ end
434
+
435
+ def find_folder_by_name(cloud_id, name)
436
+ json_response = @cloud_folders_interface.list(cloud_id, {name: name.to_s})
437
+ folders = json_response['folders']
438
+ if folders.empty?
439
+ print_red_alert "Resource Folder not found by name #{name}"
440
+ return nil
441
+ elsif folders.size > 1
442
+ matching_folders = folders.select { |it| it['name'] == name }
443
+ if matching_folders.size == 1
444
+ return matching_folders[0]
445
+ end
446
+ print_red_alert "#{folders.size} resource folders found by name #{name}"
447
+ rows = folders.collect do |it|
448
+ {id: it['id'], name: it['name']}
449
+ end
450
+ print "\n"
451
+ puts as_pretty_table(rows, [:id, :name], {color:red})
452
+ return nil
453
+ else
454
+ folder = folders[0]
455
+ # merge in tenants map
456
+ if json_response['tenants'] && json_response['tenants'][folder['id']]
457
+ folder['tenants'] = json_response['tenants'][folder['id']]
458
+ end
459
+ return folder
460
+ end
461
+ end
462
+
463
+ end