morpheus-cli 3.6.9 → 3.6.11

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 (79) hide show
  1. checksums.yaml +4 -4
  2. data/lib/morpheus/api/api_client.rb +0 -4
  3. data/lib/morpheus/api/deploy_interface.rb +12 -8
  4. data/lib/morpheus/cli.rb +0 -1
  5. data/lib/morpheus/cli/access_token_command.rb +3 -4
  6. data/lib/morpheus/cli/account_groups_command.rb +8 -3
  7. data/lib/morpheus/cli/apps.rb +10 -5
  8. data/lib/morpheus/cli/archives_command.rb +20 -6
  9. data/lib/morpheus/cli/boot_scripts_command.rb +5 -2
  10. data/lib/morpheus/cli/change_password_command.rb +1 -1
  11. data/lib/morpheus/cli/cli_command.rb +2 -4
  12. data/lib/morpheus/cli/cloud_datastores_command.rb +3 -2
  13. data/lib/morpheus/cli/commands/standard/alias_command.rb +10 -14
  14. data/lib/morpheus/cli/commands/standard/history_command.rb +0 -20
  15. data/lib/morpheus/cli/commands/standard/man_command.rb +8 -8
  16. data/lib/morpheus/cli/containers_command.rb +9 -1
  17. data/lib/morpheus/cli/credentials.rb +2 -1
  18. data/lib/morpheus/cli/cypher_command.rb +10 -2
  19. data/lib/morpheus/cli/dashboard_command.rb +1 -0
  20. data/lib/morpheus/cli/deployments.rb +5 -1
  21. data/lib/morpheus/cli/deploys.rb +26 -9
  22. data/lib/morpheus/cli/execute_schedules_command.rb +9 -1
  23. data/lib/morpheus/cli/execution_request_command.rb +3 -2
  24. data/lib/morpheus/cli/file_copy_request_command.rb +3 -2
  25. data/lib/morpheus/cli/hosts.rb +8 -6
  26. data/lib/morpheus/cli/image_builder_command.rb +14 -10
  27. data/lib/morpheus/cli/instance_types.rb +2 -1
  28. data/lib/morpheus/cli/instances.rb +9 -4
  29. data/lib/morpheus/cli/key_pairs.rb +33 -21
  30. data/lib/morpheus/cli/library_container_scripts_command.rb +5 -2
  31. data/lib/morpheus/cli/library_container_templates_command.rb +5 -2
  32. data/lib/morpheus/cli/library_container_types_command.rb +5 -3
  33. data/lib/morpheus/cli/library_instance_types_command.rb +6 -2
  34. data/lib/morpheus/cli/library_layouts_command.rb +5 -3
  35. data/lib/morpheus/cli/library_option_lists_command.rb +5 -1
  36. data/lib/morpheus/cli/library_option_types_command.rb +5 -0
  37. data/lib/morpheus/cli/library_upgrades_command.rb +5 -3
  38. data/lib/morpheus/cli/license.rb +3 -0
  39. data/lib/morpheus/cli/load_balancers.rb +10 -1
  40. data/lib/morpheus/cli/mixins/processes_helper.rb +10 -4
  41. data/lib/morpheus/cli/monitoring_apps_command.rb +32 -26
  42. data/lib/morpheus/cli/monitoring_checks_command.rb +35 -27
  43. data/lib/morpheus/cli/monitoring_contacts_command.rb +19 -16
  44. data/lib/morpheus/cli/monitoring_groups_command.rb +32 -26
  45. data/lib/morpheus/cli/monitoring_incidents_command.rb +39 -30
  46. data/lib/morpheus/cli/network_domains_command.rb +5 -2
  47. data/lib/morpheus/cli/network_groups_command.rb +5 -2
  48. data/lib/morpheus/cli/network_pool_servers_command.rb +5 -2
  49. data/lib/morpheus/cli/network_pools_command.rb +5 -2
  50. data/lib/morpheus/cli/network_proxies_command.rb +5 -2
  51. data/lib/morpheus/cli/network_services_command.rb +1 -0
  52. data/lib/morpheus/cli/networks_command.rb +5 -2
  53. data/lib/morpheus/cli/packages_command.rb +6 -3
  54. data/lib/morpheus/cli/policies_command.rb +17 -3
  55. data/lib/morpheus/cli/power_schedules_command.rb +9 -1
  56. data/lib/morpheus/cli/preseed_scripts_command.rb +5 -2
  57. data/lib/morpheus/cli/processes_command.rb +17 -6
  58. data/lib/morpheus/cli/recent_activity_command.rb +1 -0
  59. data/lib/morpheus/cli/remote.rb +1 -0
  60. data/lib/morpheus/cli/roles.rb +14 -5
  61. data/lib/morpheus/cli/security_group_rules.rb +8 -4
  62. data/lib/morpheus/cli/security_groups.rb +4 -0
  63. data/lib/morpheus/cli/shell.rb +13 -23
  64. data/lib/morpheus/cli/storage_providers_command.rb +12 -6
  65. data/lib/morpheus/cli/tasks.rb +6 -0
  66. data/lib/morpheus/cli/tenants_command.rb +13 -9
  67. data/lib/morpheus/cli/user_groups_command.rb +7 -1
  68. data/lib/morpheus/cli/user_settings_command.rb +7 -1
  69. data/lib/morpheus/cli/user_sources_command.rb +10 -8
  70. data/lib/morpheus/cli/users.rb +7 -2
  71. data/lib/morpheus/cli/version.rb +1 -1
  72. data/lib/morpheus/cli/virtual_images.rb +8 -1
  73. data/lib/morpheus/cli/whoami.rb +34 -15
  74. data/lib/morpheus/cli/workflows.rb +5 -0
  75. data/lib/morpheus/formatters.rb +4 -0
  76. data/lib/morpheus/rest_client.rb +6 -1
  77. metadata +2 -4
  78. data/lib/morpheus/api/app_templates_interface.rb +0 -64
  79. data/lib/morpheus/cli/app_templates.rb +0 -2019
@@ -68,7 +68,7 @@ class Morpheus::Cli::Whoami
68
68
  opts.on('-t','--token-only', "Print your access token only") do
69
69
  access_token_only = true
70
70
  end
71
- build_common_options(opts, options, [:json, :remote, :dry_run])
71
+ build_common_options(opts, options, [:json, :remote, :dry_run, :quiet])
72
72
  end
73
73
  optparse.parse!(args)
74
74
  connect(options)
@@ -83,10 +83,14 @@ class Morpheus::Cli::Whoami
83
83
  end
84
84
 
85
85
  if options[:dry_run]
86
- print_dry_run @api_client.whoami.dry.get(params, options)
86
+ print_dry_run @api_client.whoami.setopts(options).dry.get(params)
87
87
  return 0
88
88
  end
89
89
 
90
+ # todo: fix potential issue here, should be able to use --remote-url or --username
91
+ # in which case you do not have to be logged in (saved credentials)...
92
+ # maybe just update connect() to do @api_client = establish_remote_appliance_connection(opts)
93
+ # wallet = Morpheus::Cli::Credentials.new(@appliance_name, @appliance_url).request_credentials(options)
90
94
  wallet = Morpheus::Cli::Credentials.new(@appliance_name, @appliance_url).load_saved_credentials()
91
95
  token = wallet ? wallet['access_token'] : nil
92
96
  if !token
@@ -102,16 +106,30 @@ class Morpheus::Cli::Whoami
102
106
  end
103
107
  end
104
108
 
105
- json_response = load_whoami()
106
-
107
- user = @current_user # from load_whoami() meh
109
+ #json_response = load_whoami()
110
+
111
+ whoami_interface = @api_client.whoami.setopts(options)
112
+ whoami_response = whoami_interface.get()
113
+ json_response = whoami_response
114
+ @current_user = whoami_response["user"]
115
+ if @current_user.empty?
116
+ print_red_alert "Unauthenticated. Please login."
117
+ exit 1
118
+ end
119
+ @is_master_account = whoami_response["isMasterAccount"]
120
+ @user_permissions = whoami_response["permissions"]
108
121
 
122
+ if whoami_response["appliance"]
123
+ @appliance_build_verison = whoami_response["appliance"]["buildVersion"]
124
+ else
125
+ @appliance_build_verison = nil
126
+ end
109
127
 
110
128
  if access_token_only
111
129
  if options[:quiet]
112
- return 1
130
+ return @current_user ? 0 : 1
113
131
  end
114
- if !@access_token
132
+ if @access_token.nil?
115
133
  print yellow,"\n","No access token. Please login",reset,"\n"
116
134
  return false
117
135
  end
@@ -121,14 +139,15 @@ class Morpheus::Cli::Whoami
121
139
 
122
140
  if username_only
123
141
  if options[:quiet]
124
- return 1
142
+ return @current_user ? 0 : 1
125
143
  end
126
- if !user
127
- puts "(logged out)" # "(anonymous)" || ""
144
+ if @current_user.nil?
145
+ puts_error "(logged out)" # "(anonymous)" || ""
128
146
  return 1
147
+ else
148
+ print cyan,@current_user['username'].to_s,reset,"\n"
149
+ return 0
129
150
  end
130
- print cyan,user['username'].to_s,reset,"\n"
131
- return 0
132
151
  end
133
152
 
134
153
 
@@ -145,7 +164,7 @@ class Morpheus::Cli::Whoami
145
164
  print JSON.pretty_generate(json_response)
146
165
  print "\n"
147
166
  else
148
- if !user
167
+ if @current_user.nil?
149
168
  print yellow,"\n","No active session. Please login",reset,"\n"
150
169
  exit 1
151
170
  end
@@ -162,7 +181,7 @@ class Morpheus::Cli::Whoami
162
181
  "Username" => 'username',
163
182
  "Email" => 'email',
164
183
  "Role" => lambda {|it| format_user_role_names(it) }
165
- }, user)
184
+ }, @current_user)
166
185
  print cyan
167
186
 
168
187
  if options[:include_feature_access]
@@ -211,7 +230,7 @@ class Morpheus::Cli::Whoami
211
230
  begin
212
231
  now = Time.now.to_i
213
232
  app_map = ::Morpheus::Cli::Remote.load_remote(@appliance_name)
214
- app_map[:username] = user['username']
233
+ app_map[:username] = @current_user['username']
215
234
  app_map[:authenticated] = true
216
235
  app_map[:status] = 'ready'
217
236
  app_map[:build_version] = @appliance_build_verison if @appliance_build_verison
@@ -38,6 +38,7 @@ class Morpheus::Cli::Workflows
38
38
  begin
39
39
  params = {}
40
40
  params.merge!(parse_list_options(options))
41
+ @task_sets_interface.setopts(options)
41
42
  if options[:dry_run]
42
43
  print_dry_run @task_sets_interface.dry.get(params)
43
44
  return
@@ -127,6 +128,7 @@ class Morpheus::Cli::Workflows
127
128
  payload['taskSet']['tasks'] = tasks
128
129
  end
129
130
  end
131
+ @task_sets_interface.setopts(options)
130
132
  if options[:dry_run]
131
133
  print_dry_run @task_sets_interface.dry.create(payload)
132
134
  return
@@ -166,6 +168,7 @@ class Morpheus::Cli::Workflows
166
168
  def _get(id, options)
167
169
  workflow_name = id
168
170
  begin
171
+ @task_sets_interface.setopts(options)
169
172
  if options[:dry_run]
170
173
  if workflow_name.to_s =~ /\A\d{1,}\Z/
171
174
  print_dry_run @task_sets_interface.dry.get(workflow_name.to_i)
@@ -288,6 +291,7 @@ class Morpheus::Cli::Workflows
288
291
  payload['taskSet']['tasks'] = tasks
289
292
  end
290
293
  end
294
+ @task_sets_interface.setopts(options)
291
295
  if options[:dry_run]
292
296
  print_dry_run @task_sets_interface.dry.update(workflow['id'], payload)
293
297
  return
@@ -324,6 +328,7 @@ class Morpheus::Cli::Workflows
324
328
  unless options[:yes] || Morpheus::Cli::OptionTypes.confirm("Are you sure you want to delete the workflow #{workflow['name']}?")
325
329
  exit 1
326
330
  end
331
+ @task_sets_interface.setopts(options)
327
332
  if options[:dry_run]
328
333
  print_dry_run @task_sets_interface.dry.destroy(workflow['id'])
329
334
  return
@@ -305,3 +305,7 @@ def format_bytes_short(bytes)
305
305
  end
306
306
  return out
307
307
  end
308
+
309
+ def no_colors(str)
310
+ str.to_s.gsub /\e\[\d+m/, ""
311
+ end
@@ -18,7 +18,12 @@ module Morpheus
18
18
  end
19
19
 
20
20
  def execute(options)
21
- opts = {timeout: 30}.merge(options)
21
+ default_opts = {}
22
+ # only read requests get default 30 second timeout.
23
+ if options[:method] == :get || options[:method] == :head
24
+ default_opts[:timeout] = 30
25
+ end
26
+ opts = default_opts.merge(options)
22
27
  unless ssl_verification_enabled?
23
28
  opts[:verify_ssl] = OpenSSL::SSL::VERIFY_NONE
24
29
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: morpheus-cli
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.6.9
4
+ version: 3.6.11
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Estes
@@ -11,7 +11,7 @@ authors:
11
11
  autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
- date: 2019-02-21 00:00:00.000000000 Z
14
+ date: 2019-02-22 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: bundler
@@ -173,7 +173,6 @@ files:
173
173
  - lib/morpheus/api/account_groups_interface.rb
174
174
  - lib/morpheus/api/accounts_interface.rb
175
175
  - lib/morpheus/api/api_client.rb
176
- - lib/morpheus/api/app_templates_interface.rb
177
176
  - lib/morpheus/api/apps_interface.rb
178
177
  - lib/morpheus/api/archive_buckets_interface.rb
179
178
  - lib/morpheus/api/archive_files_interface.rb
@@ -250,7 +249,6 @@ files:
250
249
  - lib/morpheus/cli/access_token_command.rb
251
250
  - lib/morpheus/cli/account_groups_command.rb
252
251
  - lib/morpheus/cli/accounts.rb
253
- - lib/morpheus/cli/app_templates.rb
254
252
  - lib/morpheus/cli/apps.rb
255
253
  - lib/morpheus/cli/archives_command.rb
256
254
  - lib/morpheus/cli/auth_command.rb
@@ -1,64 +0,0 @@
1
- require 'morpheus/api/api_client'
2
-
3
- class Morpheus::AppTemplatesInterface < Morpheus::APIClient
4
- def initialize(access_token, refresh_token,expires_at = nil, base_url=nil)
5
- @access_token = access_token
6
- @refresh_token = refresh_token
7
- @base_url = base_url
8
- @expires_at = expires_at
9
- end
10
-
11
- def get(id)
12
- raise "#{self.class}.get() passed a blank id!" if id.to_s == ''
13
- url = "#{@base_url}/api/app-templates/#{id}"
14
- headers = { params: {}, authorization: "Bearer #{@access_token}" }
15
- execute(method: :get, url: url, headers: headers)
16
- end
17
-
18
- def list(params={})
19
- url = "#{@base_url}/api/app-templates"
20
- headers = { params: params, authorization: "Bearer #{@access_token}" }
21
- execute(method: :get, url: url, headers: headers)
22
- end
23
-
24
- def create(payload)
25
- url = "#{@base_url}/api/app-templates"
26
- headers = { :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
27
- execute(method: :post, url: url, headers: headers, payload: payload.to_json)
28
- end
29
-
30
- def update(id, payload)
31
- url = "#{@base_url}/api/app-templates/#{id}"
32
- headers = { :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
33
- execute(method: :put, url: url, headers: headers, payload: payload.to_json)
34
- end
35
-
36
- # multipart image upload
37
- def save_image(id, image_file, params={})
38
- url = "#{@base_url}/api/app-templates/#{id}/image"
39
- headers = { :params => params, :authorization => "Bearer #{@access_token}"}
40
- payload = {}
41
- payload[:templateImage] = image_file
42
- payload[:multipart] = true
43
- execute(method: :post, url: url, headers: headers, payload: payload)
44
- end
45
-
46
- def duplicate(id, payload)
47
- url = "#{@base_url}/api/app-templates/#{id}/duplicate"
48
- headers = { :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
49
- execute(method: :post, url: url, headers: headers, payload: payload.to_json)
50
- end
51
-
52
- def destroy(id)
53
- url = "#{@base_url}/api/app-templates/#{id}"
54
- headers = { :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
55
- execute(method: :delete, url: url, headers: headers)
56
- end
57
-
58
- def list_tiers(params={})
59
- url = "#{@base_url}/api/app-templates/tiers"
60
- headers = { params: params, authorization: "Bearer #{@access_token}" }
61
- execute(method: :get, url: url, headers: headers)
62
- end
63
-
64
- end
@@ -1,2019 +0,0 @@
1
- require 'io/console'
2
- require 'rest_client'
3
- require 'optparse'
4
- require 'morpheus/cli/cli_command'
5
- require 'morpheus/cli/option_types'
6
- require 'json'
7
-
8
- # deprecated and replaced with blueprints, hidden for now
9
- class Morpheus::Cli::AppTemplates
10
- include Morpheus::Cli::CliCommand
11
- include Morpheus::Cli::ProvisioningHelper
12
-
13
- set_command_hidden
14
-
15
- register_subcommands :list, :get, :add, :update, :remove
16
- register_subcommands :duplicate
17
- register_subcommands :'upload-image' => :upload_image
18
- register_subcommands :'available-tiers'
19
- register_subcommands :'add-tier', :'update-tier', :'remove-tier', :'connect-tiers', :'disconnect-tiers'
20
- register_subcommands :'add-instance'
21
- #register_subcommands :'update-instance'
22
- register_subcommands :'remove-instance'
23
- register_subcommands :'add-instance-config'
24
- #register_subcommands :'update-instance-config'
25
- register_subcommands :'remove-instance-config'
26
- # alias_subcommand :details, :get
27
- # set_default_subcommand :list
28
-
29
- def initialize()
30
- # @appliance_name, @appliance_url = Morpheus::Cli::Remote.active_appliance
31
- end
32
-
33
- def connect(opts)
34
- @api_client = establish_remote_appliance_connection(opts)
35
- @app_templates_interface = Morpheus::APIClient.new(@access_token,nil,nil, @appliance_url).app_templates
36
- @groups_interface = Morpheus::APIClient.new(@access_token,nil,nil, @appliance_url).groups
37
- @instances_interface = Morpheus::APIClient.new(@access_token,nil,nil, @appliance_url).instances
38
- @instance_types_interface = Morpheus::APIClient.new(@access_token,nil,nil, @appliance_url).instance_types
39
- @options_interface = Morpheus::APIClient.new(@access_token,nil,nil, @appliance_url).options
40
- @active_group_id = Morpheus::Cli::Groups.active_groups[@appliance_name]
41
- end
42
-
43
- def handle(args)
44
- handle_subcommand(args)
45
- end
46
-
47
- def list(args)
48
- options = {}
49
- optparse = Morpheus::Cli::OptionParser.new do |opts|
50
- opts.banner = subcommand_usage()
51
- build_common_options(opts, options, [:list, :json, :yaml, :csv, :fields, :dry_run, :remote])
52
- opts.footer = "List app templates."
53
- end
54
- optparse.parse!(args)
55
- connect(options)
56
- begin
57
- params = {}
58
- [:phrase, :offset, :max, :sort, :direction].each do |k|
59
- params[k] = options[k] unless options[k].nil?
60
- end
61
- if options[:dry_run]
62
- print_dry_run @app_templates_interface.dry.list(params)
63
- return
64
- end
65
-
66
- json_response = @app_templates_interface.list(params)
67
- app_templates = json_response['appTemplates']
68
-
69
- if options[:json]
70
- puts as_json(json_response, options, "appTemplates")
71
- return 0
72
- elsif options[:csv]
73
- puts records_as_csv(json_response['appTemplates'], options)
74
- return 0
75
- elsif options[:yaml]
76
- puts as_yaml(json_response, options, "appTemplates")
77
- return 0
78
- end
79
-
80
-
81
- title = "Morpheus App Templates"
82
- subtitles = []
83
- if params[:phrase]
84
- subtitles << "Search: #{params[:phrase]}".strip
85
- end
86
- print_h1 title, subtitles
87
- if app_templates.empty?
88
- print cyan,"No app templates found.",reset,"\n"
89
- else
90
- print_app_templates_table(app_templates, options)
91
- print_results_pagination(json_response)
92
- end
93
- print reset,"\n"
94
- return 0
95
-
96
- rescue RestClient::Exception => e
97
- print_rest_exception(e, options)
98
- exit 1
99
- end
100
- end
101
-
102
- def get(args)
103
- options = {}
104
- optparse = Morpheus::Cli::OptionParser.new do |opts|
105
- opts.banner = subcommand_usage("[template]")
106
- opts.on( '-c', '--config', "Display raw config only. Default is YAML. Combine with -j for JSON instead." ) do
107
- options[:show_config] = true
108
- end
109
- build_common_options(opts, options, [:json, :yaml, :csv, :fields, :dry_run, :remote])
110
- opts.footer = "Get details about an app template.\n" +
111
- "[template] is required. This is the name or id of an app template."
112
- end
113
- optparse.parse!(args)
114
- if args.count < 1
115
- puts optparse
116
- exit 1
117
- end
118
- connect(options)
119
- begin
120
- if options[:dry_run]
121
- if args[0].to_s =~ /\A\d{1,}\Z/
122
- print_dry_run @app_templates_interface.dry.get(args[0].to_i)
123
- else
124
- print_dry_run @app_templates_interface.dry.list({name:args[0]})
125
- end
126
- return
127
- end
128
- app_template = find_app_template_by_name_or_id(args[0])
129
- exit 1 if app_template.nil?
130
-
131
- json_response = {'appTemplate' => app_template} # skip redundant request
132
- #json_response = @app_templates_interface.get(app_template['id'])
133
- app_template = json_response['appTemplate']
134
-
135
- if options[:show_config]
136
- #print_h2 "RAW"
137
- if options[:json]
138
- print cyan
139
- print "// JSON config for Morpheus App Template: #{app_template['name']}","\n"
140
- print reset
141
- puts as_json(app_template["config"])
142
- else
143
- print cyan
144
- print "# YAML config for Morpheus App Template: #{app_template['name']}","\n"
145
- print reset
146
- puts as_yaml(app_template["config"])
147
- end
148
- return 0
149
- end
150
-
151
- if options[:json]
152
- puts as_json(json_response, options, "appTemplate")
153
- return 0
154
- elsif options[:yaml]
155
- puts as_yaml(json_response, options, "appTemplate")
156
- return 0
157
- elsif options[:csv]
158
- puts records_as_csv([json_response['appTemplate']], options)
159
- return 0
160
- end
161
-
162
- print_h1 "App Template Details"
163
-
164
- print_app_template_details(app_template)
165
-
166
- print reset,"\n"
167
-
168
- rescue RestClient::Exception => e
169
- print_rest_exception(e, options)
170
- exit 1
171
- end
172
- end
173
-
174
- def add(args)
175
- options = {}
176
- optparse = Morpheus::Cli::OptionParser.new do |opts|
177
- opts.banner = subcommand_usage("[name] [options]")
178
- opts.on('--config JSON', String, "App Template Config JSON") do |val|
179
- options['config'] = JSON.parse(val.to_s)
180
- end
181
- opts.on('--config-yaml YAML', String, "App Template Config YAML") do |val|
182
- options['config'] = YAML.load(val.to_s)
183
- end
184
- opts.on('--config-file FILE', String, "App Template Config from a local JSON or YAML file") do |val|
185
- options['configFile'] = val.to_s
186
- end
187
- build_option_type_options(opts, options, add_app_template_option_types(false))
188
- build_common_options(opts, options, [:options, :json, :dry_run, :remote])
189
- opts.footer = "Create a new app template.\n" +
190
- "[name] is optional and can be passed as --name or inside the config instead."
191
- "[--config] or [--config-file] can be used to define the app template."
192
- end
193
- optparse.parse!(args)
194
- if args.count > 1
195
- print_error Morpheus::Terminal.angry_prompt
196
- puts_error "#{command_name} add expects 0-1 arguments and received #{args.count}: #{args.join(' ')}\n#{optparse}"
197
- return 1
198
- end
199
- options[:options] ||= {}
200
- if args[0] && !options[:options]['name']
201
- options[:options]['name'] = args[0]
202
- end
203
- connect(options)
204
- begin
205
- request_payload = nil
206
- config_payload = {}
207
- if options['config']
208
- config_payload = options['config']
209
- request_payload = config_payload
210
- elsif options['configFile']
211
- config_file = File.expand_path(options['configFile'])
212
- if !File.exists?(config_file) || !File.file?(config_file)
213
- print_red_alert "File not found: #{config_file}"
214
- return false
215
- end
216
- if config_file =~ /\.ya?ml\Z/
217
- config_payload = YAML.load_file(config_file)
218
- else
219
- config_payload = JSON.parse(File.read(config_file))
220
- end
221
- request_payload = config_payload
222
- else
223
- params = Morpheus::Cli::OptionTypes.prompt(add_app_template_option_types, options[:options], @api_client, options[:params])
224
- app_template_payload = params.select {|k,v| ['name', 'description', 'category'].include?(k) }
225
- # expects no namespace, just the config
226
- request_payload = app_template_payload
227
- end
228
-
229
- if options[:dry_run]
230
- print_dry_run @app_templates_interface.dry.create(request_payload)
231
- return
232
- end
233
-
234
- json_response = @app_templates_interface.create(request_payload)
235
-
236
- if options[:json]
237
- print JSON.pretty_generate(json_response)
238
- print "\n"
239
- else
240
- app_template = json_response["appTemplate"]
241
- print_green_success "Added app template #{app_template['name']}"
242
- if !options[:no_prompt]
243
- if ::Morpheus::Cli::OptionTypes::confirm("Would you like to add a tier now?", options.merge({default: false}))
244
- add_tier([app_template['id']])
245
- while ::Morpheus::Cli::OptionTypes::confirm("Add another tier?", options.merge({default: false})) do
246
- add_tier([app_template['id']])
247
- end
248
- else
249
- # print details
250
- get([app_template['id']])
251
- end
252
- end
253
- end
254
-
255
- rescue RestClient::Exception => e
256
- print_rest_exception(e, options)
257
- exit 1
258
- end
259
- end
260
-
261
- def update(args)
262
- options = {}
263
- optparse = Morpheus::Cli::OptionParser.new do |opts|
264
- opts.banner = subcommand_usage("[template] [options]")
265
- opts.on('--config JSON', String, "App Template Config JSON") do |val|
266
- options['config'] = JSON.parse(val.to_s)
267
- end
268
- opts.on('--config-yaml YAML', String, "App Template Config YAML") do |val|
269
- options['config'] = YAML.load(val.to_s)
270
- end
271
- opts.on('--config-file FILE', String, "App Template Config from a local JSON or YAML file") do |val|
272
- options['configFile'] = val.to_s
273
- end
274
- build_option_type_options(opts, options, update_app_template_option_types(false))
275
- build_common_options(opts, options, [:options, :json, :dry_run, :quiet, :remote])
276
- opts.footer = "Update an app template.\n" +
277
- "[template] is required. This is the name or id of an app template.\n" +
278
- "[options] Available options include --name and --description. This will update only the specified values.\n" +
279
- "[--config] or [--config-file] can be used to replace the entire app template."
280
- end
281
- optparse.parse!(args)
282
-
283
- if args.count < 1
284
- puts optparse
285
- exit 1
286
- end
287
-
288
- connect(options)
289
-
290
- begin
291
-
292
- app_template = find_app_template_by_name_or_id(args[0])
293
- exit 1 if app_template.nil?
294
-
295
- request_payload = nil
296
- config_payload = {}
297
- if options['config']
298
- config_payload = options['config']
299
- request_payload = config_payload
300
- elsif options['configFile']
301
- config_file = options['configFile']
302
- if !File.exists?(config_file)
303
- print_red_alert "File not found: #{config_file}"
304
- return false
305
- end
306
- if config_file =~ /\.ya?ml\Z/
307
- config_payload = YAML.load_file(config_file)
308
- else
309
- config_payload = JSON.parse(File.read(config_file))
310
- end
311
- request_payload = config_payload
312
- else
313
- # update just name,description,category
314
- # preserve all other attributes of the config..
315
-
316
- #params = Morpheus::Cli::OptionTypes.prompt(update_app_template_option_types, options[:options], @api_client, options[:params])
317
- params = options[:options] || {}
318
-
319
- if params.empty?
320
- # print_red_alert "Specify atleast one option to update"
321
- print_red_alert "Specify atleast one option to update.\nOr use --config or --config-file to replace the entire config."
322
- puts optparse
323
- exit 1
324
- end
325
-
326
- #puts "parsed params is : #{params.inspect}"
327
- app_template_payload = params.select {|k,v| ['name','description','category'].include?(k) }
328
- # expects no namespace, just the config
329
- # preserve all other attributes of the config.
330
- request_payload = app_template["config"].merge(app_template_payload)
331
- # todo maybe: put name, description and category at the front.
332
- # request_payload = app_template_payload.merge(app_template["config"].merge(app_template_payload))
333
- end
334
-
335
- if options[:dry_run]
336
- print_dry_run @app_templates_interface.dry.update(app_template['id'], request_payload)
337
- return
338
- end
339
-
340
- json_response = @app_templates_interface.update(app_template['id'], request_payload)
341
-
342
- if options[:json]
343
- print JSON.pretty_generate(json_response)
344
- print "\n"
345
- else
346
- unless options[:quiet]
347
- app_template = json_response['appTemplate']
348
- print_green_success "Updated app template #{app_template['name']}"
349
- details_options = [app_template['id']]
350
- get(details_options)
351
- end
352
- end
353
-
354
- rescue RestClient::Exception => e
355
- print_rest_exception(e, options)
356
- exit 1
357
- end
358
- end
359
-
360
-
361
- def upload_image(args)
362
- image_type_name = nil
363
- options = {}
364
- optparse = Morpheus::Cli::OptionParser.new do |opts|
365
- opts.banner = subcommand_usage("[template] [file]")
366
- build_common_options(opts, options, [:json, :dry_run, :quiet, :remote])
367
- opts.footer = "Upload an image file to be used as the icon for an app template.\n" +
368
- "[template] is required. This is the name or id of an app template.\n" +
369
- "[file] is required. This is the local path of a file to upload [png|jpg|svg]."
370
- end
371
- optparse.parse!(args)
372
- if args.count != 2
373
- print_error Morpheus::Terminal.angry_prompt
374
- puts_error "#{command_name} upload-image expects 2 arguments and received #{args.count}: #{args.join(' ')}\n#{optparse}"
375
- return 1
376
- end
377
- app_template_name = args[0]
378
- filename = File.expand_path(args[1].to_s)
379
- image_file = nil
380
- if filename && File.file?(filename)
381
- # maybe validate it's an image file? [.png|jpg|svg]
382
- image_file = File.new(filename, 'rb')
383
- else
384
- print_red_alert "File not found: #{filename}"
385
- # print_error Morpheus::Terminal.angry_prompt
386
- # puts_error "bad argument [file] - File not found: #{filename}\n#{optparse}"
387
- return 1
388
- end
389
- connect(options)
390
- begin
391
- app_template = find_app_template_by_name_or_id(app_template_name)
392
- exit 1 if app_template.nil?
393
- if options[:dry_run]
394
- print_dry_run @app_templates_interface.dry.save_image(app_template['id'], image_file)
395
- return 0
396
- end
397
- unless options[:quiet] || options[:json]
398
- print cyan, "Uploading file #{filename} ...", reset, "\n"
399
- end
400
- json_response = @app_templates_interface.save_image(app_template['id'], image_file)
401
- if options[:json]
402
- print JSON.pretty_generate(json_response)
403
- elsif !options[:quiet]
404
- app_template = json_response['appTemplate']
405
- new_image_url = app_template['image']
406
- print cyan, "Updated app template #{app_template['name']} image.\nNew image url is: #{new_image_url}", reset, "\n\n"
407
- get([app_template['id']])
408
- end
409
- return 0
410
- rescue RestClient::Exception => e
411
- print_rest_exception(e, options)
412
- return 1
413
- end
414
- end
415
-
416
- def duplicate(args)
417
- options = {}
418
- optparse = Morpheus::Cli::OptionParser.new do |opts|
419
- opts.banner = subcommand_usage("[template] [new name]")
420
- build_common_options(opts, options, [:auto_confirm, :json, :dry_run, :remote])
421
- opts.footer = "Duplicate an app template." + "\n" +
422
- "[template] is required. This is the name or id of an app template." + "\n" +
423
- "[new name] is required. This is the name for the clone."
424
- end
425
- optparse.parse!(args)
426
-
427
- if args.count < 1
428
- puts optparse
429
- exit 1
430
- end
431
-
432
- request_payload = {"appTemplate" => {}}
433
- if args[1]
434
- request_payload["appTemplate"]["name"] = args[1]
435
- end
436
-
437
- connect(options)
438
- begin
439
- app_template = find_app_template_by_name_or_id(args[0])
440
- exit 1 if app_template.nil?
441
- # unless options[:yes] || Morpheus::Cli::OptionTypes.confirm("Are you sure you want to duplicate the app template #{app_template['name']}?")
442
- # exit
443
- # end
444
- if options[:dry_run]
445
- print_dry_run @app_templates_interface.dry.duplicate(app_template['id'], request_payload)
446
- return
447
- end
448
- json_response = @app_templates_interface.duplicate(app_template['id'], request_payload)
449
-
450
- if options[:json]
451
- print JSON.pretty_generate(json_response)
452
- print "\n"
453
- else
454
- new_app_template = json_response["appTemplate"] || {}
455
- print_green_success "Created duplicate app template '#{new_app_template['name']}'"
456
- #get([new_app_template["id"]])
457
- end
458
-
459
- rescue RestClient::Exception => e
460
- print_rest_exception(e, options)
461
- exit 1
462
- end
463
- end
464
-
465
- def remove(args)
466
- options = {}
467
- optparse = Morpheus::Cli::OptionParser.new do |opts|
468
- opts.banner = subcommand_usage("[template]")
469
- build_common_options(opts, options, [:auto_confirm, :json, :dry_run, :remote])
470
- opts.footer = "Delete an app template." + "\n" +
471
- "[template] is required. This is the name or id of an app template."
472
- end
473
- optparse.parse!(args)
474
-
475
- if args.count < 1
476
- puts optparse
477
- exit 1
478
- end
479
-
480
- connect(options)
481
- begin
482
- app_template = find_app_template_by_name_or_id(args[0])
483
- exit 1 if app_template.nil?
484
- unless options[:yes] || Morpheus::Cli::OptionTypes.confirm("Are you sure you want to delete the app template #{app_template['name']}?")
485
- exit
486
- end
487
- if options[:dry_run]
488
- print_dry_run @app_templates_interface.dry.destroy(app_template['id'])
489
- return
490
- end
491
- json_response = @app_templates_interface.destroy(app_template['id'])
492
-
493
- if options[:json]
494
- print JSON.pretty_generate(json_response)
495
- print "\n"
496
- else
497
- print_green_success "Removed app template #{app_template['name']}"
498
- end
499
-
500
- rescue RestClient::Exception => e
501
- print_rest_exception(e, options)
502
- exit 1
503
- end
504
- end
505
-
506
- def add_instance(args)
507
- options = {}
508
- optparse = Morpheus::Cli::OptionParser.new do |opts|
509
- opts.banner = subcommand_usage("[template] [tier] [instance-type]")
510
- # opts.on( '-g', '--group GROUP', "Group" ) do |val|
511
- # options[:group] = val
512
- # end
513
- # opts.on( '-c', '--cloud CLOUD', "Cloud" ) do |val|
514
- # options[:cloud] = val
515
- # end
516
- opts.on('--name VALUE', String, "Instance Name") do |val|
517
- options[:instance_name] = val
518
- end
519
- build_common_options(opts, options, [:options, :json, :dry_run, :remote])
520
- opts.footer = "Update an app template, adding an instance." + "\n" +
521
- "[template] is required. This is the name or id of an app template." + "\n" +
522
- "[tier] is required and will be prompted for. This is the name of the tier." + "\n" +
523
- "[instance-type] is required and will be prompted for. This is the type of instance."
524
- end
525
- optparse.parse!(args)
526
-
527
- if args.count < 1
528
- print_error Morpheus::Terminal.angry_prompt
529
- puts_error "#{command_name} add-instance expects 3 arguments and received #{args.count}: #{args.join(' ')}\n#{optparse}"
530
- return 1
531
- end
532
-
533
- connect(options)
534
-
535
- begin
536
- app_template_name = args[0]
537
- tier_name = args[1]
538
- instance_type_code = args[2]
539
- # we also need consider when there is multiple instances of the same type in
540
- # a template/tier.. so maybe split instance_type_code as [type-code]:[index].. or...errr
541
-
542
- app_template = find_app_template_by_name_or_id(app_template_name)
543
- return 1 if app_template.nil?
544
-
545
- if !tier_name
546
- v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'tierName', 'fieldLabel' => 'Tier Name', 'type' => 'text', 'required' => true, 'description' => 'Enter the name of the tier'}], options[:options])
547
- tier_name = v_prompt['tierName']
548
- end
549
-
550
- if !instance_type_code
551
- instance_type_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'type', 'type' => 'select', 'fieldLabel' => 'Type', 'optionSource' => 'instanceTypes', 'required' => true, 'description' => 'Select Instance Type.'}],options[:options],api_client,{})
552
- instance_type_code = instance_type_prompt['type']
553
- end
554
- instance_type = find_instance_type_by_code(instance_type_code)
555
- return 1 if instance_type.nil?
556
-
557
- tier_config = nil
558
- instance_config = nil
559
-
560
- app_template["config"] ||= {}
561
- tiers = app_template["config"]["tiers"]
562
- tiers ||= {}
563
- # tier identified by name, case sensitive...
564
- if !tiers[tier_name]
565
- tiers[tier_name] = {}
566
- end
567
- tier_config = tiers[tier_name]
568
-
569
- tier_config['instances'] ||= []
570
- instance_config = tier_config['instances'].find {|it| it["instance"] && it["instance"]["type"] && it["instance"]["type"] == instance_type["code"] }
571
- if !instance_config
572
- instance_config = {'instance' => {'type' => instance_type['code']} }
573
- tier_config['instances'].push(instance_config)
574
- end
575
- instance_config['instance'] ||= {}
576
-
577
- # just prompts for Instance Name (optional)
578
- instance_name = nil
579
- if options[:instance_name]
580
- instance_name = options[:instance_name]
581
- else
582
- v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'name', 'fieldLabel' => 'Instance Name', 'type' => 'text', 'defaultValue' => instance_config['instance']['name']}])
583
- instance_name = v_prompt['name'] || ''
584
- end
585
-
586
- if instance_name
587
- if instance_name.to_s == 'null'
588
- instance_config['instance'].delete('name')
589
- # instance_config['instance']['name'] = ''
590
- else
591
- instance_config['instance']['name'] = instance_name
592
- end
593
- end
594
-
595
- # ok, make api request
596
- app_template["config"]["tiers"] = tiers
597
- request_payload = {appTemplate: app_template}
598
-
599
- if options[:dry_run]
600
- print_dry_run @app_templates_interface.dry.update(app_template['id'], request_payload)
601
- return 0
602
- end
603
-
604
- json_response = @app_templates_interface.update(app_template['id'], request_payload)
605
-
606
- if options[:json]
607
- puts JSON.pretty_generate(json_response)
608
- elsif !options[:quiet]
609
- print_green_success "Instance added to app template #{app_template['name']}"
610
- # prompt for new instance
611
- if !options[:no_prompt]
612
- if ::Morpheus::Cli::OptionTypes::confirm("Would you like to add a config now?", options.merge({default: true}))
613
- # todo: this needs to work by index, because you can have multiple instances of the same type
614
- add_instance_config([app_template['id'], tier_name, instance_type['code']])
615
- while ::Morpheus::Cli::OptionTypes::confirm("Add another config?", options.merge({default: false})) do
616
- add_instance_config([app_template['id'], tier_name, instance_type['code']])
617
- end
618
- else
619
- # print details
620
- get([app_template['name']])
621
- end
622
- end
623
- end
624
- return 0
625
-
626
- rescue RestClient::Exception => e
627
- print_rest_exception(e, options)
628
- return 1
629
- end
630
-
631
- end
632
-
633
- def add_instance_config(args)
634
- options = {}
635
- optparse = Morpheus::Cli::OptionParser.new do |opts|
636
- opts.banner = subcommand_usage("[template] [tier] [instance]")
637
- opts.on( '-g', '--group GROUP', "Group" ) do |val|
638
- options[:group] = val
639
- end
640
- opts.on( '-c', '--cloud CLOUD', "Cloud" ) do |val|
641
- options[:cloud] = val
642
- end
643
- opts.on( '-e', '--env ENVIRONMENT', "Environment" ) do |val|
644
- options[:environment] = val
645
- end
646
- opts.on('--name VALUE', String, "Instance Name") do |val|
647
- options[:instance_name] = val
648
- end
649
- build_common_options(opts, options, [:options, :json, :dry_run, :remote])
650
- opts.footer = "Update an app template, adding an instance config." + "\n" +
651
- "[template] is required. This is the name or id of an app template." + "\n" +
652
- "[tier] is required. This is the name of the tier." + "\n" +
653
- "[instance] is required. This is the type of instance."
654
- end
655
- optparse.parse!(args)
656
-
657
- if args.count < 3
658
- print_error Morpheus::Terminal.angry_prompt
659
- puts_error "Wrong number of arguments"
660
- puts_error optparse
661
- return 1
662
- end
663
-
664
- connect(options)
665
-
666
- begin
667
-
668
- app_template_name = args[0]
669
- tier_name = args[1]
670
- instance_type_code = args[2]
671
- # we also need consider when there is multiple instances of the same type in
672
- # a template/tier.. so maybe split instance_type_code as [type-code]:[index].. or...errr
673
-
674
- app_template = find_app_template_by_name_or_id(app_template_name)
675
- return 1 if app_template.nil?
676
-
677
- instance_type = find_instance_type_by_code(instance_type_code)
678
- return 1 if instance_type.nil?
679
-
680
- tier_config = nil
681
- instance_config = nil
682
-
683
- app_template["config"] ||= {}
684
- tiers = app_template["config"]["tiers"]
685
- tiers ||= {}
686
- # tier identified by name, case sensitive...
687
- if !tiers[tier_name]
688
- tiers[tier_name] = {}
689
- end
690
- tier_config = tiers[tier_name]
691
-
692
- tier_config['instances'] ||= []
693
- instance_config = tier_config['instances'].find {|it| it["instance"] && it["instance"]["type"] && it["instance"]["type"] == instance_type["code"] }
694
- if !instance_config
695
- instance_config = {'instance' => {'type' => instance_type['code']} }
696
- tier_config['instances'].push(instance_config)
697
- end
698
- instance_config['instance'] ||= {}
699
-
700
- # group prompt
701
-
702
- # use active group by default
703
- options[:group] ||= @active_group_id
704
-
705
-
706
- # available_groups = get_available_groups()
707
- # group_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'group', 'fieldLabel' => 'Group', 'type' => 'select', 'selectOptions' => get_available_groups(), 'required' => true, 'defaultValue' => @active_group_id}],options[:options],@api_client,{})
708
-
709
- # group_id = group_prompt['group']
710
- # the_group = find_group_by_name_or_id_for_provisioning(group_id)
711
-
712
- # # cloud prompt
713
- # cloud_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'cloud', 'type' => 'select', 'fieldLabel' => 'Cloud', 'optionSource' => 'clouds', 'required' => true, 'description' => 'Select Cloud.'}],options[:options],@api_client,{groupId: group_id})
714
- # cloud_id = cloud_prompt['cloud']
715
-
716
- # look for existing config for group + cloud
717
-
718
- options[:name_required] = false
719
- options[:instance_type_code] = instance_type["code"]
720
-
721
- #options[:options].deep_merge!(specific_config)
722
- # this provisioning helper method handles all (most) of the parsing and prompting
723
- instance_config_payload = prompt_new_instance(options)
724
-
725
- # strip all empty string and nil, would be problematic for update()
726
- instance_config_payload.deep_compact!
727
-
728
- # puts "INSTANCE CONFIG YAML:"
729
- # puts as_yaml(instance_config_payload)
730
-
731
- selected_environment = instance_config_payload.delete('instanceContext') || instance_config_payload.delete('environment')
732
- # groom provision instance payload for template purposes
733
- selected_cloud_id = instance_config_payload.delete('zoneId')
734
- selected_site = instance_config_payload['instance'].delete('site')
735
- selected_site_id = selected_site['id']
736
-
737
- selected_group = find_group_by_name_or_id_for_provisioning(selected_site_id)
738
- selected_cloud = find_cloud_by_name_or_id_for_provisioning(selected_group['id'], selected_cloud_id)
739
-
740
- # store config in environments => env => groups => groupname => clouds => cloudname =>
741
- current_config = instance_config
742
- if selected_environment.to_s != ''
743
- instance_config['environments'] ||= {}
744
- instance_config['environments'][selected_environment] ||= {}
745
- current_config = instance_config['environments'][selected_environment]
746
- end
747
-
748
- current_config['groups'] ||= {}
749
- current_config['groups'][selected_group['name']] ||= {}
750
- current_config['groups'][selected_group['name']]['clouds'] ||= {}
751
- current_config['groups'][selected_group['name']]['clouds'][selected_cloud['name']] = instance_config_payload
752
-
753
- # ok, make api request
754
- app_template["config"]["tiers"] = tiers
755
- request_payload = {appTemplate: app_template}
756
-
757
- if options[:dry_run]
758
- print_dry_run @app_templates_interface.dry.update(app_template['id'], request_payload)
759
- return 0
760
- end
761
-
762
- json_response = @app_templates_interface.update(app_template['id'], request_payload)
763
-
764
- if options[:json]
765
- puts JSON.pretty_generate(json_response)
766
- else
767
- print_green_success "Instance added to app template #{app_template['name']}"
768
- get([app_template['name']])
769
- end
770
- return 0
771
-
772
- rescue RestClient::Exception => e
773
- print_rest_exception(e, options)
774
- return 1
775
- end
776
-
777
- end
778
-
779
- def remove_instance_config(args)
780
- instance_index = nil
781
- options = {}
782
- optparse = Morpheus::Cli::OptionParser.new do |opts|
783
- opts.banner = subcommand_usage("[template] [tier] [instance] -g GROUP -c CLOUD")
784
- opts.on( '-g', '--group GROUP', "Group" ) do |val|
785
- options[:group] = val
786
- end
787
- opts.on( '-c', '--cloud CLOUD', "Cloud" ) do |val|
788
- options[:cloud] = val
789
- end
790
- opts.on( '-e', '--env ENV', "Environment" ) do |val|
791
- options[:environment] = val
792
- end
793
- # opts.on( nil, '--index NUMBER', "The index of the instance to remove, starting with 0." ) do |val|
794
- # instance_index = val.to_i
795
- # end
796
- build_common_options(opts, options, [:auto_confirm, :json, :dry_run, :remote])
797
- opts.footer = "Update an app template, removing a specified instance config." + "\n" +
798
- "[template] is required. This is the name or id of an app template." + "\n" +
799
- "[tier] is required. This is the name of the tier." + "\n" +
800
- "[instance] is required. This is the type of instance." + "\n" +
801
- "The config scope is specified with the -g GROUP, -c CLOUD and -e ENV. The -g and -c options are required."
802
- end
803
- optparse.parse!(args)
804
-
805
- if args.count < 3
806
- print_error Morpheus::Terminal.angry_prompt
807
- puts_error "Wrong number of arguments"
808
- puts_error optparse
809
- return 1
810
- end
811
- if !options[:group]
812
- print_error Morpheus::Terminal.angry_prompt
813
- puts_error "Missing required argument -g GROUP"
814
- puts_error optparse
815
- return 1
816
- end
817
- if !options[:cloud]
818
- print_error Morpheus::Terminal.angry_prompt
819
- puts_error "Missing required argument -g CLOUD"
820
- puts_error optparse
821
- return 1
822
- end
823
- connect(options)
824
-
825
- begin
826
-
827
- app_template_name = args[0]
828
- tier_name = args[1]
829
- instance_type_code = args[2]
830
- # we also need consider when there is multiple instances of the same type in
831
- # a template/tier.. so maybe split instance_type_code as [type-code]:[index].. or...errr
832
-
833
- app_template = find_app_template_by_name_or_id(app_template_name)
834
- return 1 if app_template.nil?
835
-
836
- instance_type = find_instance_type_by_code(instance_type_code)
837
- return 1 if instance_type.nil?
838
-
839
- tier_config = nil
840
- # instance_config = nil
841
-
842
- app_template["config"] ||= {}
843
- tiers = app_template["config"]["tiers"]
844
- tiers ||= {}
845
- # tier identified by name, case sensitive...
846
- if !tiers[tier_name]
847
- print_red_alert "Tier not found by name #{tier_name}"
848
- return 1
849
- end
850
- tier_config = tiers[tier_name]
851
-
852
- if !tier_config
853
- print_red_alert "Tier not found by name #{tier1_name}!"
854
- return 1
855
- elsif tier_config['instances'].nil? || tier_config['instances'].empty?
856
- print_red_alert "Tier #{tier_name} is empty!"
857
- return 1
858
- end
859
-
860
- matching_indices = []
861
- if tier_config['instances']
862
- if instance_index
863
- matching_indices = [instance_index].compact
864
- else
865
- tier_config['instances'].each_with_index do |instance_config, index|
866
- is_match = instance_config['instance'] && instance_config['instance']['type'] == instance_type['code']
867
- if is_match
868
- matching_indices << index
869
- end
870
- end
871
- end
872
- end
873
-
874
- if matching_indices.size == 0
875
- print_red_alert "Instance not found by tier: #{tier_name}, type: #{instance_type_code}"
876
- return 1
877
- elsif matching_indices.size > 1
878
- #print_error Morpheus::Terminal.angry_prompt
879
- print_red_alert "More than one instance found by tier: #{tier_name}, type: #{instance_type_code}"
880
- puts_error "Try using the --index option to identify the instance you wish to remove."
881
- puts_error optparse
882
- return 1
883
- end
884
-
885
- # ok, find the specified config
886
- instance_config = tier_config['instances'][matching_indices[0]]
887
- parent_config = nil
888
- current_config = instance_config
889
- delete_key = nil
890
-
891
- config_description = "type: #{instance_type['code']}"
892
- config_description << " environment: #{options[:environment]}" if options[:environment]
893
- config_description << " group: #{options[:group]}" if options[:group]
894
- config_description << " cloud: #{options[:cloud]}" if options[:cloud]
895
- config_description = config_description.strip
896
-
897
-
898
- # find config in environments => env => groups => groupname => clouds => cloudname =>
899
- if options[:environment]
900
- if current_config && current_config['environments'] && current_config['environments'][options[:environment]]
901
- parent_config = current_config['environments']
902
- delete_key = options[:environment]
903
- current_config = parent_config[delete_key]
904
- else
905
- print_red_alert "Instance config not found for scope #{config_description}"
906
- return 1
907
- end
908
- end
909
- if options[:group]
910
- if current_config && current_config['groups'] && current_config['groups'][options[:group]]
911
- parent_config = current_config['groups']
912
- delete_key = options[:group]
913
- current_config = parent_config[delete_key]
914
- else
915
- print_red_alert "Instance config not found for scope #{config_description}"
916
- return 1
917
- end
918
- end
919
- if options[:cloud]
920
- if current_config && current_config['clouds'] && current_config['clouds'][options[:cloud]]
921
- parent_config = current_config['clouds']
922
- delete_key = options[:cloud]
923
- current_config = parent_config[delete_key]
924
- else
925
- print_red_alert "Instance config not found for scope #{config_description}"
926
- return 1
927
- end
928
- end
929
-
930
- # remove it
931
- parent_config.delete(delete_key)
932
-
933
- unless options[:yes] || Morpheus::Cli::OptionTypes.confirm("Are you sure you want to delete this instance config #{config_description} ?")
934
- return 9
935
- end
936
-
937
- # ok, make api request
938
- app_template["config"]["tiers"] = tiers
939
- request_payload = {appTemplate: app_template}
940
-
941
- if options[:dry_run]
942
- print_dry_run @app_templates_interface.dry.update(app_template['id'], request_payload)
943
- return
944
- end
945
- json_response = @app_templates_interface.update(app_template['id'], request_payload)
946
-
947
- if options[:json]
948
- puts JSON.pretty_generate(json_response)
949
- else
950
- print_green_success "Removed instance from app template."
951
- get([app_template['id']])
952
- end
953
- return 0
954
-
955
- rescue RestClient::Exception => e
956
- print_rest_exception(e, options)
957
- exit 1
958
- end
959
- end
960
-
961
- def update_instance(args)
962
- print_red_alert "NOT YET SUPPORTED"
963
- return 5
964
- end
965
-
966
- def update_instance_config(args)
967
- print_red_alert "NOT YET SUPPORTED"
968
- return 5
969
- end
970
-
971
- def remove_instance(args)
972
- instance_index = nil
973
- options = {}
974
- optparse = Morpheus::Cli::OptionParser.new do |opts|
975
- opts.banner = subcommand_usage("[template] [tier] [instance]")
976
- # opts.on('--index NUMBER', Number, "Identify Instance by index within tier, starting with 0." ) do |val|
977
- # instance_index = val.to_i
978
- # end
979
- build_common_options(opts, options, [:auto_confirm, :json, :dry_run, :remote])
980
- end
981
- optparse.parse!(args)
982
-
983
- if args.count < 3
984
- print_error Morpheus::Terminal.angry_prompt
985
- puts_error "Wrong number of arguments"
986
- puts_error optparse
987
- return 1
988
- end
989
-
990
- connect(options)
991
-
992
- begin
993
-
994
- app_template_name = args[0]
995
- tier_name = args[1]
996
- instance_identier = args[2]
997
-
998
- # instance_type_code = args[2]
999
- # we also need consider when there is multiple instances of the same type in
1000
- # a template/tier.. so maybe split instance_type_code as [type-code]:[index].. or...errr
1001
-
1002
- app_template = find_app_template_by_name_or_id(app_template_name)
1003
- return 1 if app_template.nil?
1004
-
1005
- # instance_type = find_instance_type_by_code(instance_type_code)
1006
- # return 1 if instance_type.nil?
1007
-
1008
- tier_config = nil
1009
- # instance_config = nil
1010
-
1011
- app_template["config"] ||= {}
1012
- tiers = app_template["config"]["tiers"]
1013
- tiers ||= {}
1014
- # tier identified by name, case sensitive...
1015
- if !tiers[tier_name]
1016
- print_red_alert "Tier not found by name #{tier_name}"
1017
- return 1
1018
- end
1019
- tier_config = tiers[tier_name]
1020
-
1021
- if tier_config['instances'].nil? || tier_config['instances'].empty?
1022
- print_red_alert "Tier #{tier_name} is empty!"
1023
- return 1
1024
- end
1025
-
1026
- # find instance
1027
- matching_indices = []
1028
- if tier_config['instances']
1029
- if instance_identier.to_s =~ /\A\d{1,}\Z/
1030
- matching_indices = [instance_identier.to_i].compact
1031
- else
1032
- tier_config['instances'].each_with_index do |instance_config, index|
1033
- if instance_config['instance'] && instance_config['instance']['type'] == instance_identier
1034
- matching_indices << index
1035
- elsif instance_config['instance'] && instance_config['instance']['name'] == instance_identier
1036
- matching_indices << index
1037
- end
1038
- end
1039
- end
1040
- end
1041
- if matching_indices.size == 0
1042
- print_red_alert "Instance not found by tier: #{tier_name}, instance: #{instance_identier}"
1043
- return 1
1044
- elsif matching_indices.size > 1
1045
- #print_error Morpheus::Terminal.angry_prompt
1046
- print_red_alert "More than one instance matched tier: #{tier_name}, instance: #{instance_identier}"
1047
- puts_error "Instance can be identified type, name or index within the tier."
1048
- puts_error optparse
1049
- return 1
1050
- end
1051
-
1052
- # remove it
1053
- tier_config['instances'].delete_at(matching_indices[0])
1054
-
1055
- unless options[:yes] || Morpheus::Cli::OptionTypes.confirm("Are you sure you want to delete this instance #{instance_type_code} instance from tier: #{tier_name}?")
1056
- return 9
1057
- end
1058
-
1059
- # ok, make api request
1060
- app_template["config"]["tiers"] = tiers
1061
- request_payload = {appTemplate: app_template}
1062
-
1063
- if options[:dry_run]
1064
- print_dry_run @app_templates_interface.dry.update(app_template['id'], request_payload)
1065
- return
1066
- end
1067
- json_response = @app_templates_interface.update(app_template['id'], request_payload)
1068
-
1069
- if options[:json]
1070
- puts JSON.pretty_generate(json_response)
1071
- else
1072
- print_green_success "Removed instance from app template."
1073
- get([app_template['id']])
1074
- end
1075
- return 0
1076
-
1077
- rescue RestClient::Exception => e
1078
- print_rest_exception(e, options)
1079
- exit 1
1080
- end
1081
- end
1082
-
1083
- def add_tier(args)
1084
- options = {}
1085
- boot_order = nil
1086
- linked_tiers = nil
1087
- optparse = Morpheus::Cli::OptionParser.new do |opts|
1088
- opts.banner = subcommand_usage("[template] [tier]")
1089
- opts.on('--name VALUE', String, "Tier Name") do |val|
1090
- options[:name] = val
1091
- end
1092
- opts.on('--bootOrder NUMBER', String, "Boot Order" ) do |val|
1093
- boot_order = val
1094
- end
1095
- opts.on('--linkedTiers x,y,z', Array, "Connected Tiers.") do |val|
1096
- linked_tiers = val
1097
- end
1098
- build_common_options(opts, options, [:options, :json, :dry_run, :remote])
1099
- end
1100
- optparse.parse!(args)
1101
-
1102
- if args.count < 1
1103
- print_error Morpheus::Terminal.angry_prompt
1104
- puts_error "#{command_name} add-tier requires argument: [template]\n#{optparse}"
1105
- # puts optparse
1106
- return 1
1107
- end
1108
- app_template_name = args[0]
1109
- tier_name = args[1]
1110
-
1111
- connect(options)
1112
-
1113
- begin
1114
- app_template = find_app_template_by_name_or_id(app_template_name)
1115
- return 1 if app_template.nil?
1116
-
1117
- app_template["config"] ||= {}
1118
- app_template["config"]["tiers"] ||= {}
1119
- tiers = app_template["config"]["tiers"]
1120
-
1121
- # prompt new tier
1122
- # Name
1123
- # {'fieldName' => 'name', 'fieldLabel' => 'Name', 'type' => 'text', 'required' => true, 'displayOrder' => 1, 'description' => 'A unique name for the app template.'},
1124
- # {'fieldName' => 'bootOrder', 'fieldLabel' => 'Boot Order', 'type' => 'text', 'required' => false, 'displayOrder' => 2, 'description' => 'Boot Order'}
1125
- if !tier_name
1126
- v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'name', 'fieldLabel' => 'Tier Name', 'type' => 'text', 'required' => true, 'description' => 'Enter the name of the tier'}], options[:options])
1127
- tier_name = v_prompt['name']
1128
- end
1129
- # case insensitive match
1130
- existing_tier_names = tiers.keys
1131
- matching_tier_name = existing_tier_names.find {|k| k.downcase == tier_name.downcase }
1132
- if matching_tier_name
1133
- # print_red_alert "Tier #{tier_name} already exists"
1134
- # return 1
1135
- print cyan,"Tier #{tier_name} already exists.",reset,"\n"
1136
- return 0
1137
- end
1138
- # idempotent
1139
- if !tiers[tier_name]
1140
- tiers[tier_name] = {'instances' => []}
1141
- end
1142
- tier = tiers[tier_name]
1143
-
1144
- # Boot Order
1145
- if !boot_order
1146
- v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'bootOrder', 'fieldLabel' => 'Boot Order', 'type' => 'text', 'required' => false, 'description' => 'Sequence order for starting app instances by tier. 0-N', 'defaultValue' => tier['bootOrder']}], options[:options])
1147
- boot_order = v_prompt['bootOrder']
1148
- end
1149
- if boot_order.to_s == 'null'
1150
- tier.delete('bootOrder')
1151
- elsif boot_order.to_s != ''
1152
- tier['bootOrder'] = boot_order.to_i
1153
- end
1154
-
1155
- # Connected Tiers
1156
- if !linked_tiers
1157
- v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'linkedTiers', 'fieldLabel' => 'Connected Tiers', 'type' => 'text', 'required' => false, 'description' => 'Names of connected tiers, comma separated', 'defaultValue' => (linked_tiers ? linked_tiers.join(',') : nil)}], options[:options])
1158
- linked_tiers = v_prompt['linkedTiers'].to_s.split(',').collect {|it| it.strip }.select {|it| it != ''}
1159
- end
1160
- if linked_tiers && !linked_tiers.empty?
1161
- linked_tiers.each do |other_tier_name|
1162
- link_result = link_tiers(tiers, [tier_name, other_tier_name])
1163
- # could just re-prompt unless options[:no_prompt]
1164
- return 1 if !link_result
1165
- end
1166
- end
1167
-
1168
- # ok, make api request
1169
- app_template["config"]["tiers"] = tiers
1170
- request_payload = app_template["config"]
1171
- # request_payload = {appTemplate: app_template}
1172
-
1173
- if options[:dry_run]
1174
- print_dry_run @app_templates_interface.dry.update(app_template['id'], request_payload)
1175
- return
1176
- end
1177
- json_response = @app_templates_interface.update(app_template['id'], request_payload)
1178
-
1179
- if options[:json]
1180
- puts JSON.pretty_generate(json_response)
1181
- elsif !options[:quiet]
1182
- print_green_success "Added tier #{tier_name}"
1183
- # prompt for new instance
1184
- if !options[:no_prompt]
1185
- if ::Morpheus::Cli::OptionTypes::confirm("Would you like to add an instance now?", options.merge({default: true}))
1186
- add_instance([app_template['id'], tier_name])
1187
- while ::Morpheus::Cli::OptionTypes::confirm("Add another instance now?", options.merge({default: false})) do
1188
- add_instance([app_template['id'], tier_name])
1189
- end
1190
- # if !add_instance_result
1191
- # end
1192
- end
1193
- end
1194
- # print details
1195
- get([app_template['name']])
1196
- end
1197
- return 0
1198
- rescue RestClient::Exception => e
1199
- print_rest_exception(e, options)
1200
- exit 1
1201
- end
1202
- end
1203
-
1204
- def update_tier(args)
1205
- options = {}
1206
- new_tier_name = nil
1207
- boot_order = nil
1208
- linked_tiers = nil
1209
- optparse = Morpheus::Cli::OptionParser.new do |opts|
1210
- opts.banner = subcommand_usage("[template] [tier]")
1211
- opts.on('--name VALUE', String, "Tier Name") do |val|
1212
- new_tier_name = val
1213
- end
1214
- opts.on('--bootOrder NUMBER', String, "Boot Order" ) do |val|
1215
- boot_order = val
1216
- end
1217
- opts.on('--linkedTiers x,y,z', Array, "Connected Tiers") do |val|
1218
- linked_tiers = val
1219
- end
1220
- build_common_options(opts, options, [:options, :json, :dry_run, :remote])
1221
- end
1222
- optparse.parse!(args)
1223
-
1224
- if args.count != 2
1225
- print_error Morpheus::Terminal.angry_prompt
1226
- puts_error "#{command_name} update-tier expects 2 arguments and received #{args.count}: #{args.join(' ')}\n#{optparse}"
1227
- return 1
1228
- end
1229
- app_template_name = args[0]
1230
- tier_name = args[1]
1231
-
1232
- connect(options)
1233
-
1234
- begin
1235
- app_template = find_app_template_by_name_or_id(app_template_name)
1236
- return 1 if app_template.nil?
1237
-
1238
- app_template["config"] ||= {}
1239
- app_template["config"]["tiers"] ||= {}
1240
- tiers = app_template["config"]["tiers"]
1241
-
1242
- if !tiers[tier_name]
1243
- print_red_alert "Tier not found by name #{tier_name}"
1244
- return 1
1245
- end
1246
- tier = tiers[tier_name]
1247
-
1248
-
1249
- if options[:no_prompt]
1250
- if !(new_tier_name || boot_order || linked_tiers)
1251
- print_error Morpheus::Terminal.angry_prompt
1252
- puts_error "#{command_name} update-tier requires an option to update.\n#{optparse}"
1253
- return 1
1254
- end
1255
- end
1256
-
1257
- # prompt update tier
1258
- # Name
1259
- if !new_tier_name
1260
- v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'name', 'fieldLabel' => 'Tier Name', 'type' => 'text', 'required' => true, 'description' => 'Rename the tier', 'defaultValue' => tier_name}], options[:options])
1261
- new_tier_name = v_prompt['name']
1262
- end
1263
- if new_tier_name && new_tier_name != tier_name
1264
- old_tier_name = tier_name
1265
- if tiers[new_tier_name]
1266
- print_red_alert "A tier named #{tier_name} already exists."
1267
- return 1
1268
- end
1269
- tier = tiers.delete(tier_name)
1270
- tiers[new_tier_name] = tier
1271
- # Need to fix all the linkedTiers
1272
- tiers.each do |k, v|
1273
- if v['linkedTiers'] && v['linkedTiers'].include?(tier_name)
1274
- v['linkedTiers'] = v['linkedTiers'].map {|it| it == tier_name ? new_tier_name : it }
1275
- end
1276
- end
1277
- # old_tier_name = tier_name
1278
- tier_name = new_tier_name
1279
- end
1280
-
1281
- # Boot Order
1282
- if !boot_order
1283
- v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'bootOrder', 'fieldLabel' => 'Boot Order', 'type' => 'text', 'required' => false, 'description' => 'Sequence order for starting app instances by tier. 0-N', 'defaultValue' => tier['bootOrder']}], options[:options])
1284
- boot_order = v_prompt['bootOrder']
1285
- end
1286
- if boot_order.to_s == 'null'
1287
- tier.delete('bootOrder')
1288
- elsif boot_order.to_s != ''
1289
- tier['bootOrder'] = boot_order.to_i
1290
- end
1291
-
1292
- # Connected Tiers
1293
- if !linked_tiers
1294
- v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'linkedTiers', 'fieldLabel' => 'Connected Tiers', 'type' => 'text', 'required' => false, 'description' => 'Names of connected tiers, comma separated', 'defaultValue' => (tier['linkedTiers'] ? tier['linkedTiers'].join(',') : nil)}], options[:options])
1295
- linked_tiers = v_prompt['linkedTiers'].to_s.split(',').collect {|it| it.strip }.select {|it| it != ''}
1296
- end
1297
- current_linked_tiers = tier['linkedTiers'] || []
1298
- if linked_tiers && linked_tiers != current_linked_tiers
1299
- remove_tiers = current_linked_tiers - linked_tiers
1300
- remove_tiers.each do |other_tier_name|
1301
- unlink_result = unlink_tiers(tiers, [tier_name, other_tier_name])
1302
- # could just re-prompt unless options[:no_prompt]
1303
- return 1 if !unlink_result
1304
- end
1305
- add_tiers = linked_tiers - current_linked_tiers
1306
- add_tiers.each do |other_tier_name|
1307
- link_result = link_tiers(tiers, [tier_name, other_tier_name])
1308
- # could just re-prompt unless options[:no_prompt]
1309
- return 1 if !link_result
1310
- end
1311
- end
1312
-
1313
- # ok, make api request
1314
- app_template["config"]["tiers"] = tiers
1315
- request_payload = app_template["config"]
1316
- # request_payload = {appTemplate: app_template}
1317
-
1318
- if options[:dry_run]
1319
- print_dry_run @app_templates_interface.dry.update(app_template['id'], request_payload)
1320
- return
1321
- end
1322
- json_response = @app_templates_interface.update(app_template['id'], request_payload)
1323
-
1324
- if options[:json]
1325
- puts JSON.pretty_generate(json_response)
1326
- elsif !options[:quiet]
1327
- print_green_success "Updated tier #{tier_name}"
1328
- get([app_template['id']])
1329
- end
1330
- return 0
1331
- rescue RestClient::Exception => e
1332
- print_rest_exception(e, options)
1333
- exit 1
1334
- end
1335
- end
1336
-
1337
- def remove_tier(args)
1338
- options = {}
1339
- optparse = Morpheus::Cli::OptionParser.new do |opts|
1340
- opts.banner = subcommand_usage("[template] [tier]")
1341
- build_common_options(opts, options, [:auto_confirm, :json, :dry_run, :remote])
1342
- end
1343
- optparse.parse!(args)
1344
-
1345
- if args.count < 2
1346
- print_error Morpheus::Terminal.angry_prompt
1347
- puts_error "#{command_name} remove-tier expects 2 arguments and received #{args.count}: #{args.join(' ')}\n#{optparse}"
1348
- return 1
1349
- end
1350
- app_template_name = args[0]
1351
- tier_name = args[1]
1352
-
1353
- connect(options)
1354
-
1355
- begin
1356
- app_template = find_app_template_by_name_or_id(app_template_name)
1357
- return 1 if app_template.nil?
1358
-
1359
- app_template["config"] ||= {}
1360
- app_template["config"]["tiers"] ||= {}
1361
- tiers = app_template["config"]["tiers"]
1362
-
1363
- if !tiers[tier_name]
1364
- # print_red_alert "Tier not found by name #{tier_name}"
1365
- # return 1
1366
- print cyan,"Tier #{tier_name} does not exist.",reset,"\n"
1367
- return 0
1368
- end
1369
-
1370
- unless options[:yes] || Morpheus::Cli::OptionTypes.confirm("Are you sure you want to delete the tier #{tier_name}?")
1371
- exit
1372
- end
1373
-
1374
- # remove it
1375
- tiers.delete(tier_name)
1376
-
1377
- # ok, make api request
1378
- app_template["config"]["tiers"] = tiers
1379
- request_payload = app_template["config"]
1380
- # request_payload = {appTemplate: app_template}
1381
-
1382
- if options[:dry_run]
1383
- print_dry_run @app_templates_interface.dry.update(app_template['id'], request_payload)
1384
- return
1385
- end
1386
-
1387
- json_response = @app_templates_interface.update(app_template['id'], request_payload)
1388
-
1389
-
1390
- if options[:json]
1391
- print JSON.pretty_generate(json_response)
1392
- print "\n"
1393
- else
1394
- print_green_success "Removed tier #{tier_name}"
1395
- get([app_template['name']])
1396
- end
1397
-
1398
- rescue RestClient::Exception => e
1399
- print_rest_exception(e, options)
1400
- exit 1
1401
- end
1402
- end
1403
-
1404
- def connect_tiers(args)
1405
- options = {}
1406
- optparse = Morpheus::Cli::OptionParser.new do |opts|
1407
- opts.banner = subcommand_usage("[template] [Tier1] [Tier2]")
1408
- build_common_options(opts, options, [:json, :dry_run, :remote])
1409
- end
1410
- optparse.parse!(args)
1411
-
1412
- if args.count < 3
1413
- print_error Morpheus::Terminal.angry_prompt
1414
- puts_error "#{command_name} connect-tiers expects 3 arguments and received #{args.count}: #{args.join(' ')}\n#{optparse}"
1415
- # puts optparse
1416
- return 1
1417
- end
1418
- app_template_name = args[0]
1419
- tier1_name = args[1]
1420
- tier2_name = args[2]
1421
-
1422
- connect(options)
1423
-
1424
- begin
1425
- app_template = find_app_template_by_name_or_id(app_template_name)
1426
- return 1 if app_template.nil?
1427
-
1428
- app_template["config"] ||= {}
1429
- tiers = app_template["config"]["tiers"]
1430
-
1431
- if !tiers || tiers.keys.size == 0
1432
- error_msg = "App Template #{app_template['name']} has no tiers."
1433
- # print_red_alert "App Template #{app_template['name']} has no tiers."
1434
- # raise_command_error "App Template #{app_template['name']} has no tiers."
1435
- print_error Morpheus::Terminal.angry_prompt
1436
- puts_error "App Template #{app_template['name']} has no tiers."
1437
- return 1
1438
- end
1439
-
1440
- connect_tiers = []
1441
- tier1 = tiers[tier1_name]
1442
- tier2 = tiers[tier2_name]
1443
- # uhh support N args
1444
-
1445
- if tier1.nil?
1446
- print_red_alert "Tier not found by name #{tier1_name}!"
1447
- return 1
1448
- end
1449
-
1450
- if tier2.nil?
1451
- print_red_alert "Tier not found by name #{tier2_name}!"
1452
- return 1
1453
- end
1454
-
1455
- tier1["linkedTiers"] = tier1["linkedTiers"] || []
1456
- tier2["linkedTiers"] = tier2["linkedTiers"] || []
1457
-
1458
- found_edge = tier1["linkedTiers"].include?(tier2_name) || tier2["linkedTiers"].include?(tier1_name)
1459
-
1460
- if found_edge
1461
- puts cyan,"Tiers #{tier1_name} and #{tier2_name} are already connected.",reset
1462
- return 0
1463
- end
1464
-
1465
- # ok to be connect the tiers
1466
- # note: the ui doesn't hook up both sides eh?
1467
-
1468
- if !tier1["linkedTiers"].include?(tier2_name)
1469
- tier1["linkedTiers"].push(tier2_name)
1470
- end
1471
-
1472
- if !tier2["linkedTiers"].include?(tier1_name)
1473
- tier2["linkedTiers"].push(tier1_name)
1474
- end
1475
-
1476
- # ok, make api request
1477
- app_template["config"]["tiers"] = tiers
1478
- request_payload = app_template["config"]
1479
- # request_payload = {appTemplate: app_template}
1480
-
1481
- if options[:dry_run]
1482
- print_dry_run @app_templates_interface.dry.update(app_template['id'], request_payload)
1483
- return
1484
- end
1485
- json_response = @app_templates_interface.update(app_template['id'], request_payload)
1486
-
1487
-
1488
- if options[:json]
1489
- print JSON.pretty_generate(json_response)
1490
- print "\n"
1491
- else
1492
- print_green_success "Connected 2 tiers for app template #{app_template['name']}"
1493
- get([app_template['name']])
1494
- end
1495
-
1496
- rescue RestClient::Exception => e
1497
- print_rest_exception(e, options)
1498
- exit 1
1499
- end
1500
- end
1501
-
1502
- def disconnect_tiers(args)
1503
- options = {}
1504
- optparse = Morpheus::Cli::OptionParser.new do |opts|
1505
- opts.banner = subcommand_usage("[template] [Tier1] [Tier2]")
1506
- build_common_options(opts, options, [:json, :dry_run, :remote])
1507
- end
1508
- optparse.parse!(args)
1509
-
1510
- if args.count < 3
1511
- print_error Morpheus::Terminal.angry_prompt
1512
- puts_error "#{command_name} disconnect-tiers expects 3 arguments and received #{args.count}: #{args.join(' ')}\n#{optparse}"
1513
- # puts optparse
1514
- return 1
1515
- end
1516
- app_template_name = args[0]
1517
- tier1_name = args[1]
1518
- tier2_name = args[2]
1519
-
1520
- connect(options)
1521
-
1522
- begin
1523
- app_template = find_app_template_by_name_or_id(app_template_name)
1524
- return 1 if app_template.nil?
1525
-
1526
- app_template["config"] ||= {}
1527
- tiers = app_template["config"]["tiers"]
1528
-
1529
- if !tiers || tiers.keys.size == 0
1530
- # print_red_alert "App Template #{app_template['name']} has no tiers."
1531
- # raise_command_error "App Template #{app_template['name']} has no tiers."
1532
- print_error Morpheus::Terminal.angry_prompt
1533
- puts_error "App Template #{app_template['name']} has no tiers."
1534
- return 1
1535
- end
1536
-
1537
- connect_tiers = []
1538
- tier1 = tiers[tier1_name]
1539
- tier2 = tiers[tier2_name]
1540
- # uhh support N args
1541
-
1542
- if tier1.nil?
1543
- print_red_alert "Tier not found by name #{tier1_name}!"
1544
- return 1
1545
- end
1546
-
1547
- if tier2.nil?
1548
- print_red_alert "Tier not found by name #{tier2_name}!"
1549
- return 1
1550
- end
1551
-
1552
- tier1["linkedTiers"] = tier1["linkedTiers"] || []
1553
- tier2["linkedTiers"] = tier2["linkedTiers"] || []
1554
-
1555
- found_edge = tier1["linkedTiers"].include?(tier2_name) || tier2["linkedTiers"].include?(tier1_name)
1556
-
1557
- if found_edge
1558
- puts cyan,"Tiers #{tier1_name} and #{tier2_name} are not connected.",reset
1559
- return 0
1560
- end
1561
-
1562
- # remove links
1563
- tier1["linkedTiers"] = tier1["linkedTiers"].reject {|it| it == tier2_name }
1564
- tier2["linkedTiers"] = tier2["linkedTiers"].reject {|it| it == tier1_name }
1565
-
1566
- # ok, make api request
1567
- app_template["config"]["tiers"] = tiers
1568
- request_payload = app_template["config"]
1569
- # request_payload = {appTemplate: app_template}
1570
-
1571
- if options[:dry_run]
1572
- print_dry_run @app_templates_interface.dry.update(app_template['id'], request_payload)
1573
- return
1574
- end
1575
- json_response = @app_templates_interface.update(app_template['id'], request_payload)
1576
-
1577
-
1578
- if options[:json]
1579
- print JSON.pretty_generate(json_response)
1580
- print "\n"
1581
- else
1582
- print_green_success "Connected 2 tiers for app template #{app_template['name']}"
1583
- get([app_template['name']])
1584
- end
1585
-
1586
- rescue RestClient::Exception => e
1587
- print_rest_exception(e, options)
1588
- exit 1
1589
- end
1590
- end
1591
-
1592
- def available_tiers(args)
1593
- options = {}
1594
- optparse = Morpheus::Cli::OptionParser.new do |opts|
1595
- opts.banner = subcommand_usage()
1596
- build_common_options(opts, options, [:json, :dry_run, :remote])
1597
- end
1598
- optparse.parse!(args)
1599
- connect(options)
1600
- params = {}
1601
-
1602
- begin
1603
- if options[:dry_run]
1604
- print_dry_run @app_templates_interface.dry.list_tiers(params)
1605
- return
1606
- end
1607
- json_response = @app_templates_interface.list_tiers(params)
1608
- tiers = json_response["tiers"] # just a list of names
1609
- if options[:json]
1610
- puts JSON.pretty_generate(json_response)
1611
- else
1612
- print_h1 "Available Tiers"
1613
- if tiers.empty?
1614
- print yellow,"No tiers found.",reset,"\n"
1615
- else
1616
- # rows = tiers.collect do |tier|
1617
- # {
1618
- # id: tier['id'],
1619
- # name: tier['name'],
1620
- # }
1621
- # end
1622
- # print cyan
1623
- # tp rows, [:name]
1624
- print cyan
1625
- tiers.each do |tier_name|
1626
- puts tier_name
1627
- end
1628
- end
1629
- print reset,"\n"
1630
- end
1631
- return 0
1632
- rescue RestClient::Exception => e
1633
- print_rest_exception(e, options)
1634
- exit 1
1635
- end
1636
-
1637
- end
1638
-
1639
- private
1640
-
1641
-
1642
- def add_app_template_option_types(connected=true)
1643
- [
1644
- {'fieldName' => 'name', 'fieldLabel' => 'Name', 'type' => 'text', 'required' => true, 'displayOrder' => 1},
1645
- {'fieldName' => 'description', 'fieldLabel' => 'Description', 'type' => 'text', 'required' => false, 'displayOrder' => 2},
1646
- {'fieldName' => 'category', 'fieldLabel' => 'Category', 'type' => 'text', 'required' => false, 'displayOrder' => 3},
1647
- #{'fieldName' => 'group', 'fieldLabel' => 'Group', 'type' => 'select', 'selectOptions' => (connected ? get_available_groups() : []), 'required' => true}
1648
- ]
1649
- end
1650
-
1651
- def update_app_template_option_types(connected=true)
1652
- list = add_app_template_option_types(connected)
1653
- list = list.reject {|it| ["group"].include? it['fieldName'] }
1654
- list.each {|it| it['required'] = false }
1655
- list
1656
- end
1657
-
1658
- def find_app_template_by_name_or_id(val)
1659
- if val.to_s =~ /\A\d{1,}\Z/
1660
- return find_app_template_by_id(val)
1661
- else
1662
- return find_app_template_by_name(val)
1663
- end
1664
- end
1665
-
1666
- def find_app_template_by_id(id)
1667
- begin
1668
- json_response = @app_templates_interface.get(id.to_i)
1669
- return json_response['appTemplate']
1670
- rescue RestClient::Exception => e
1671
- if e.response && e.response.code == 404
1672
- print_red_alert "App Template not found by id #{id}"
1673
- else
1674
- raise e
1675
- end
1676
- end
1677
- end
1678
-
1679
- def find_app_template_by_name(name)
1680
- app_templates = @app_templates_interface.list({name: name.to_s})['appTemplates']
1681
- if app_templates.empty?
1682
- print_red_alert "App Template not found by name #{name}"
1683
- return nil
1684
- elsif app_templates.size > 1
1685
- print_red_alert "#{app_templates.size} app templates by name #{name}"
1686
- print_app_templates_table(app_templates, {color: red})
1687
- print reset,"\n"
1688
- return nil
1689
- else
1690
- return app_templates[0]
1691
- end
1692
- end
1693
-
1694
- def find_group_by_name(name)
1695
- group_results = @groups_interface.get(name)
1696
- if group_results['groups'].empty?
1697
- print_red_alert "Group not found by name #{name}"
1698
- return nil
1699
- end
1700
- return group_results['groups'][0]
1701
- end
1702
-
1703
- def find_cloud_by_name(group_id, name)
1704
- option_results = @options_interface.options_for_source('clouds',{groupId: group_id})
1705
- match = option_results['data'].find { |grp| grp['value'].to_s == name.to_s || grp['name'].downcase == name.downcase}
1706
- if match.nil?
1707
- print_red_alert "Cloud not found by name #{name}"
1708
- return nil
1709
- else
1710
- return match['value']
1711
- end
1712
- end
1713
-
1714
- def print_app_templates_table(app_templates, opts={})
1715
- table_color = opts[:color] || cyan
1716
- rows = app_templates.collect do |app_template|
1717
- #instance_type_names = (app_template['instanceTypes'] || []).collect {|it| it['name'] }.join(', ')
1718
- instance_type_names = []
1719
- # if app_template['config'] && app_template['config']["tiers"]
1720
- # app_template['config']["tiers"]
1721
- # end
1722
- {
1723
- id: app_template['id'],
1724
- name: app_template['name'],
1725
- description: app_template['description'],
1726
- category: app_template['category'],
1727
- tiers_summary: format_app_template_tiers_summary(app_template)
1728
- }
1729
- end
1730
-
1731
- term_width = current_terminal_width()
1732
- tiers_col_width = 60
1733
- if term_width > 190
1734
- tiers_col_width += 130
1735
- end
1736
- columns = [
1737
- :id,
1738
- :name,
1739
- :description,
1740
- :category,
1741
- {:tiers_summary => {:display_name => "TIERS", :max_width => tiers_col_width} }
1742
- ]
1743
- if opts[:include_fields]
1744
- columns = opts[:include_fields]
1745
- end
1746
- print table_color
1747
- print as_pretty_table(rows, columns, opts)
1748
- print reset
1749
- end
1750
-
1751
- def generate_id(len=16)
1752
- id = ""
1753
- len.times { id << (1 + rand(9)).to_s }
1754
- id
1755
- end
1756
-
1757
- def format_app_template_tiers_summary(app_template)
1758
- # don't use colors here, or cell truncation will not work
1759
- str = ""
1760
- if app_template["config"] && app_template["config"]["tiers"]
1761
- tier_descriptions = app_template["config"]["tiers"].collect do |tier_name, tier_config|
1762
- # maybe do Tier Name (instance, instance2)
1763
- instance_blurbs = []
1764
- if tier_config["instances"]
1765
- tier_config["instances"].each do |instance_config|
1766
- if instance_config["instance"] && instance_config["instance"]["type"]
1767
- # only have type: code in the config, rather not name fetch remotely right now..
1768
- # instance_blurbs << instance_config["instance"]["type"]
1769
- instance_name = instance_config["instance"]["name"] || ""
1770
- instance_type_code = instance_config["instance"]["type"]
1771
- instances_str = "#{instance_type_code}"
1772
- if instance_name.to_s != ""
1773
- instances_str << " - #{instance_name}"
1774
- end
1775
- begin
1776
- config_list = parse_scoped_instance_configs(instance_config)
1777
- if config_list.size == 0
1778
- instances_str << " (No configs)"
1779
- elsif config_list.size == 1
1780
- # configs_str = config_list.collect {|it|
1781
- # str = ""
1782
- # it[:scope].to_s.inspect
1783
- # }.join(", ")
1784
- the_config = config_list[0]
1785
- scope_str = the_config[:scope].collect {|k,v| v.to_s }.join("/")
1786
- instances_str << " (#{scope_str})"
1787
- else
1788
- instances_str << " (#{config_list.size} configs)"
1789
- end
1790
- rescue => err
1791
- puts_error "Failed to parse instance scoped instance configs: #{err.class} #{err.message}"
1792
- raise err
1793
- end
1794
- instance_blurbs << instances_str
1795
- end
1796
- end
1797
- end
1798
- if instance_blurbs.size > 0
1799
- tier_name + ": #{instance_blurbs.join(', ')}"
1800
- else
1801
- tier_name + ": (empty)"
1802
- end
1803
- end
1804
- str += tier_descriptions.compact.join(", ")
1805
- end
1806
- str
1807
- end
1808
-
1809
- def print_app_template_details(app_template)
1810
- print cyan
1811
- description_cols = {
1812
- "ID" => 'id',
1813
- "Name" => 'name',
1814
- "Description" => 'description',
1815
- "Category" => 'category',
1816
- #"Image" => lambda {|it| it['config'] ? it['config']['image'] : '' },
1817
- }
1818
- if app_template["config"] && app_template["config"]["image"]
1819
- description_cols["Image"] = lambda {|it| app_template["config"]["image"] }
1820
- # else
1821
- # '/assets/apps/template.png'
1822
- end
1823
- print_description_list(description_cols, app_template)
1824
- # print_h2 "Tiers"
1825
- if app_template["config"] && app_template["config"]["tiers"] && app_template["config"]["tiers"].keys.size != 0
1826
- print cyan
1827
- #puts as_yaml(app_template["config"]["tiers"])
1828
- app_template["config"]["tiers"].each do |tier_name, tier_config|
1829
- # print_h2 "Tier: #{tier_name}"
1830
- print_h2 tier_name
1831
- # puts " Instances:"
1832
- if tier_config['instances'] && tier_config['instances'].size != 0
1833
- # puts as_yaml(tier)
1834
- tier_config['instances'].each_with_index do |instance_config, instance_index|
1835
- instance_name = instance_config["instance"]["name"] || ""
1836
- instance_type_code = ""
1837
- if instance_config["instance"]["type"]
1838
- instance_type_code = instance_config["instance"]["type"]
1839
- end
1840
- instance_bullet = ""
1841
- # instance_bullet += "#{green} - #{bold}#{instance_type_code}#{reset}"
1842
- instance_bullet += "#{green}#{bold}#{instance_type_code}#{reset}"
1843
- if instance_name.to_s != ""
1844
- instance_bullet += "#{green} - #{instance_name}#{reset}"
1845
- end
1846
- puts instance_bullet
1847
- # print "\n"
1848
- begin
1849
- config_list = parse_scoped_instance_configs(instance_config)
1850
- print cyan
1851
- if config_list.size > 0
1852
- print "\n"
1853
- if config_list.size == 1
1854
- puts " Config:"
1855
- else
1856
- puts " Configs (#{config_list.size}):"
1857
- end
1858
- config_list.each do |config_obj|
1859
- # puts " = #{config_obj[:scope].inspect}"
1860
- config_scope = config_obj[:scope]
1861
- scoped_instance_config = config_obj[:config]
1862
- config_description = ""
1863
- config_items = []
1864
- if config_scope[:environment]
1865
- config_items << {label: "Environment", value: config_scope[:environment]}
1866
- end
1867
- if config_scope[:group]
1868
- config_items << {label: "Group", value: config_scope[:group]}
1869
- end
1870
- if config_scope[:cloud]
1871
- config_items << {label: "Cloud", value: config_scope[:cloud]}
1872
- end
1873
- # if scoped_instance_config['plan'] && scoped_instance_config['plan']['code']
1874
- # config_items << {label: "Plan", value: scoped_instance_config['plan']['code']}
1875
- # end
1876
- config_description = config_items.collect {|item| "#{item[:label]}: #{item[:value]}"}.join(", ")
1877
- puts " * #{config_description}"
1878
- end
1879
- else
1880
- print white," Instance has no configs, see `app-templates add-instance-config \"#{app_template['name']}\" \"#{tier_name}\" \"#{instance_type_code}\"`",reset,"\n"
1881
- end
1882
- rescue => err
1883
- #puts_error "Failed to parse instance scoped instance configs for app template #{app_template['id']} #{app_template['name']} Exception: #{err.class} #{err.message}"
1884
- end
1885
- print "\n"
1886
- #puts as_yaml(instance_config)
1887
- # todo: iterate over
1888
- # instance_config["groups"][group_name]["clouds"][cloud_name]
1889
- end
1890
-
1891
- print cyan
1892
- if tier_config['bootOrder']
1893
- puts "Boot Order: #{tier_config['bootOrder']}"
1894
- end
1895
- if tier_config['linkedTiers'] && !tier_config['linkedTiers'].empty?
1896
- puts "Connected Tiers: #{tier_config['linkedTiers'].join(', ')}"
1897
- end
1898
-
1899
- else
1900
- print white," Tier is empty, see `app-templates add-instance \"#{app_template['name']}\" \"#{tier_name}\"`",reset,"\n"
1901
- end
1902
- # print "\n"
1903
-
1904
- end
1905
- # print "\n"
1906
-
1907
- else
1908
- print white,"\nTemplate is empty, see `app-templates add-tier \"#{app_template['name']}\"`",reset,"\n"
1909
- end
1910
- end
1911
-
1912
- # this parses the environments => groups => clouds tree structure
1913
- # and returns a list of objects like {scope: {group:'thegroup'}, config: Map}
1914
- # this would be be better as a recursive function, brute forced for now.
1915
- def parse_scoped_instance_configs(instance_config)
1916
- config_list = []
1917
- if instance_config['environments'] && instance_config['environments'].keys.size > 0
1918
- instance_config['environments'].each do |env_name, env_config|
1919
- if env_config['groups']
1920
- env_config['groups'].each do |group_name, group_config|
1921
- if group_config['clouds'] && !group_config['clouds'].empty?
1922
- group_config['clouds'].each do |cloud_name, cloud_config|
1923
- config_list << {config: cloud_config, scope: {environment: env_name, group: group_name, cloud: cloud_name}}
1924
- end
1925
- end
1926
- if (!group_config['clouds'] || group_config['clouds'].empty?)
1927
- config_list << {config: group_config, scope: {environment: env_name, group: group_name}}
1928
- end
1929
- end
1930
- end
1931
- if env_config['clouds'] && !env_config['clouds'].empty?
1932
- env_config['clouds'].each do |cloud_name, cloud_config|
1933
- config_list << {config: cloud_config, scope: {environment: env_name, cloud: cloud_name}}
1934
- end
1935
- end
1936
- if (!env_config['groups'] || env_config['groups'].empty?) && (!env_config['clouds'] || env_config['clouds'].empty?)
1937
- config_list << {config: env_config, scope: {environment: env_name}}
1938
- end
1939
- end
1940
- end
1941
- if instance_config['groups']
1942
- instance_config['groups'].each do |group_name, group_config|
1943
- if group_config['clouds'] && !group_config['clouds'].empty?
1944
- group_config['clouds'].each do |cloud_name, cloud_config|
1945
- config_list << {config: cloud_config, scope: {group: group_name, cloud: cloud_name}}
1946
- end
1947
- end
1948
- if (!group_config['clouds'] || group_config['clouds'].empty?)
1949
- config_list << {config: group_config, scope: {group: group_name}}
1950
- end
1951
- end
1952
- end
1953
- if instance_config['clouds']
1954
- instance_config['clouds'].each do |cloud_name, cloud_config|
1955
- config_list << {config: cloud_config, scope: {cloud: cloud_name}}
1956
- end
1957
- end
1958
- return config_list
1959
- end
1960
-
1961
- def link_tiers(tiers, tier_names)
1962
- # tiers = app_template["config"]["tiers"]
1963
- tier_names = [tier_names].flatten.collect {|it| it }.compact.uniq
1964
- if !tiers
1965
- print_red_alert "No tiers found for template"
1966
- return false
1967
- end
1968
-
1969
- existing_tier_names = tiers.keys
1970
- matching_tier_names = tier_names.map {|tier_name|
1971
- existing_tier_names.find {|k| k.downcase == tier_name.downcase }
1972
- }.compact
1973
- if matching_tier_names.size != tier_names.size
1974
- print_red_alert "Template does not contain tiers: '#{tier_names}'"
1975
- return false
1976
- end
1977
- matching_tier_names.each do |tier_name|
1978
- tier = tiers[tier_name]
1979
- tier['linkedTiers'] ||= []
1980
- other_tier_names = matching_tier_names.select {|it| tier_name != it}
1981
- other_tier_names.each do |other_tier_name|
1982
- if !tier['linkedTiers'].include?(other_tier_name)
1983
- tier['linkedTiers'].push(other_tier_name)
1984
- end
1985
- end
1986
- end
1987
- return true
1988
- end
1989
-
1990
- def unlink_tiers(tiers, tier_names)
1991
- # tiers = app_template["config"]["tiers"]
1992
- tier_names = [tier_names].flatten.collect {|it| it }.compact.uniq
1993
- if !tiers
1994
- print_red_alert "No tiers found for template"
1995
- return false
1996
- end
1997
-
1998
- existing_tier_names = tiers.keys
1999
- matching_tier_names = tier_names.map {|tier_name|
2000
- existing_tier_names.find {|k| k.downcase == tier_name.downcase }
2001
- }.compact
2002
- if matching_tier_names.size != tier_names.size
2003
- print_red_alert "Template does not contain tiers: '#{tier_names}'"
2004
- return false
2005
- end
2006
- matching_tier_names.each do |tier_name|
2007
- tier = tiers[tier_name]
2008
- tier['linkedTiers'] ||= []
2009
- other_tier_names = matching_tier_names.select {|it| tier_name != it}
2010
- other_tier_names.each do |other_tier_name|
2011
- if tier['linkedTiers'].include?(other_tier_name)
2012
- tier['linkedTiers'] = tier['linkedTiers'].reject {|it| it == other_tier_name }
2013
- end
2014
- end
2015
- end
2016
- return true
2017
- end
2018
-
2019
- end