morpheus-cli 3.6.28 → 3.6.29

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