morpheus-cli 2.12.5 → 3.1.0

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