morpheus-cli 2.12.5 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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