morpheus-cli 3.5.2 → 3.5.3
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.
- checksums.yaml +4 -4
- data/lib/morpheus/api/api_client.rb +16 -0
- data/lib/morpheus/api/blueprints_interface.rb +84 -0
- data/lib/morpheus/api/execution_request_interface.rb +33 -0
- data/lib/morpheus/api/instances_interface.rb +21 -0
- data/lib/morpheus/api/packages_interface.rb +25 -5
- data/lib/morpheus/api/processes_interface.rb +34 -0
- data/lib/morpheus/api/roles_interface.rb +7 -0
- data/lib/morpheus/api/servers_interface.rb +8 -0
- data/lib/morpheus/api/user_settings_interface.rb +76 -0
- data/lib/morpheus/cli.rb +5 -1
- data/lib/morpheus/cli/alias_command.rb +1 -1
- data/lib/morpheus/cli/app_templates.rb +2 -1
- data/lib/morpheus/cli/apps.rb +173 -19
- data/lib/morpheus/cli/blueprints_command.rb +2134 -0
- data/lib/morpheus/cli/cli_command.rb +3 -1
- data/lib/morpheus/cli/clouds.rb +4 -10
- data/lib/morpheus/cli/coloring_command.rb +14 -8
- data/lib/morpheus/cli/containers_command.rb +92 -5
- data/lib/morpheus/cli/execution_request_command.rb +313 -0
- data/lib/morpheus/cli/hosts.rb +188 -7
- data/lib/morpheus/cli/instances.rb +472 -9
- data/lib/morpheus/cli/login.rb +1 -1
- data/lib/morpheus/cli/mixins/print_helper.rb +8 -0
- data/lib/morpheus/cli/mixins/processes_helper.rb +134 -0
- data/lib/morpheus/cli/option_types.rb +21 -16
- data/lib/morpheus/cli/packages_command.rb +469 -17
- data/lib/morpheus/cli/processes_command.rb +313 -0
- data/lib/morpheus/cli/remote.rb +20 -9
- data/lib/morpheus/cli/roles.rb +186 -6
- data/lib/morpheus/cli/shell.rb +10 -1
- data/lib/morpheus/cli/tasks.rb +4 -1
- data/lib/morpheus/cli/user_settings_command.rb +431 -0
- data/lib/morpheus/cli/version.rb +1 -1
- data/lib/morpheus/cli/whoami.rb +1 -1
- data/lib/morpheus/formatters.rb +14 -0
- data/lib/morpheus/morpkg.rb +119 -0
- data/morpheus-cli.gemspec +1 -0
- metadata +26 -2
data/lib/morpheus/cli/login.rb
CHANGED
@@ -87,7 +87,7 @@ class Morpheus::Cli::Login
|
|
87
87
|
|
88
88
|
if creds
|
89
89
|
if !options[:quiet]
|
90
|
-
print green,"Logged in to #{@appliance_name} as #{::Morpheus::Cli::Remote.
|
90
|
+
print green,"Logged in to #{@appliance_name} as #{::Morpheus::Cli::Remote.load_remote(@appliance_name)[:username]}#{reset}", reset, "\n"
|
91
91
|
end
|
92
92
|
return 0 # , nil
|
93
93
|
else
|
@@ -807,4 +807,12 @@ module Morpheus::Cli::PrintHelper
|
|
807
807
|
end
|
808
808
|
end
|
809
809
|
|
810
|
+
def sleep_with_dots(sleep_seconds, dots=3, dot_chr=".")
|
811
|
+
dot_interval = (sleep_seconds.to_f / dots.to_i)
|
812
|
+
dots.to_i.times do |dot_index|
|
813
|
+
sleep dot_interval
|
814
|
+
print dot_chr
|
815
|
+
end
|
816
|
+
end
|
817
|
+
|
810
818
|
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
require 'table_print'
|
2
|
+
require 'morpheus/cli/mixins/print_helper'
|
3
|
+
|
4
|
+
# Mixin for Morpheus::Cli command classes
|
5
|
+
# Provides common methods for fetching and printing accounts, roles, and users.
|
6
|
+
# The including class must establish @accounts_interface, @roles_interface, @users_interface
|
7
|
+
module Morpheus::Cli::ProcessesHelper
|
8
|
+
|
9
|
+
def self.included(klass)
|
10
|
+
klass.send :include, Morpheus::Cli::PrintHelper
|
11
|
+
end
|
12
|
+
|
13
|
+
|
14
|
+
def print_process_details(process)
|
15
|
+
description_cols = {
|
16
|
+
"Process ID" => lambda {|it| it['id'] },
|
17
|
+
"Name" => lambda {|it| it['displayName'] },
|
18
|
+
"Description" => lambda {|it| it['description'] },
|
19
|
+
"Process Type" => lambda {|it| it['processType'] ? (it['processType']['name'] || it['processType']['code']) : it['processTypeName'] },
|
20
|
+
"Created By" => lambda {|it| it['createdBy'] ? (it['createdBy']['displayName'] || it['createdBy']['username']) : '' },
|
21
|
+
"Start Date" => lambda {|it| format_local_dt(it['startDate']) },
|
22
|
+
"End Date" => lambda {|it| format_local_dt(it['endDate']) },
|
23
|
+
"Duration" => lambda {|it| format_process_duration(it) },
|
24
|
+
"Status" => lambda {|it| format_process_status(it) },
|
25
|
+
# "# Events" => lambda {|it| (it['events'] || []).size() },
|
26
|
+
}
|
27
|
+
print_description_list(description_cols, process)
|
28
|
+
|
29
|
+
if process['error']
|
30
|
+
print_h2 "Error"
|
31
|
+
print reset
|
32
|
+
#puts format_process_error(process_event)
|
33
|
+
puts process['error'].to_s.strip
|
34
|
+
end
|
35
|
+
|
36
|
+
if process['output']
|
37
|
+
print_h2 "Output"
|
38
|
+
print reset
|
39
|
+
#puts format_process_error(process_event)
|
40
|
+
puts process['output'].to_s.strip
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def print_process_event_details(process_event)
|
45
|
+
# process_event =~ process
|
46
|
+
description_cols = {
|
47
|
+
"Process ID" => lambda {|it| it['processId'] },
|
48
|
+
"Event ID" => lambda {|it| it['id'] },
|
49
|
+
"Name" => lambda {|it| it['displayName'] },
|
50
|
+
"Description" => lambda {|it| it['description'] },
|
51
|
+
"Process Type" => lambda {|it| it['processType'] ? (it['processType']['name'] || it['processType']['code']) : it['processTypeName'] },
|
52
|
+
"Created By" => lambda {|it| it['createdBy'] ? (it['createdBy']['displayName'] || it['createdBy']['username']) : '' },
|
53
|
+
"Start Date" => lambda {|it| format_local_dt(it['startDate']) },
|
54
|
+
"End Date" => lambda {|it| format_local_dt(it['endDate']) },
|
55
|
+
"Duration" => lambda {|it| format_process_duration(it) },
|
56
|
+
"Status" => lambda {|it| format_process_status(it) },
|
57
|
+
}
|
58
|
+
print_description_list(description_cols, process_event)
|
59
|
+
|
60
|
+
if process_event['error']
|
61
|
+
print_h2 "Error"
|
62
|
+
print reset
|
63
|
+
#puts format_process_error(process_event)
|
64
|
+
puts process_event['error'].to_s.strip
|
65
|
+
end
|
66
|
+
|
67
|
+
if process_event['output']
|
68
|
+
print_h2 "Output"
|
69
|
+
print reset
|
70
|
+
#puts format_process_error(process_event)
|
71
|
+
puts process_event['output'].to_s.strip
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
|
76
|
+
def format_process_status(process, return_color=cyan)
|
77
|
+
out = ""
|
78
|
+
status_string = process['status'].to_s
|
79
|
+
if status_string == 'complete'
|
80
|
+
out << "#{green}#{status_string.upcase}#{return_color}"
|
81
|
+
elsif status_string == 'failed'
|
82
|
+
out << "#{red}#{status_string.upcase}#{return_color}"
|
83
|
+
elsif status_string == 'expired'
|
84
|
+
out << "#{red}#{status_string.upcase}#{return_color}"
|
85
|
+
else
|
86
|
+
out << "#{cyan}#{status_string.upcase}#{return_color}"
|
87
|
+
end
|
88
|
+
out
|
89
|
+
end
|
90
|
+
|
91
|
+
# decolorize, remove newlines and truncate for table cell
|
92
|
+
def format_process_error(process, max_length=50, return_color=cyan)
|
93
|
+
out = ""
|
94
|
+
if process['error']
|
95
|
+
lines = process['error'].split("\n").collect {|line| reset + "#{line.to_s.strip}" }
|
96
|
+
out = lines.join(" ")
|
97
|
+
if max_length
|
98
|
+
out = truncate_string(out, max_length)
|
99
|
+
end
|
100
|
+
out << return_color if return_color
|
101
|
+
end
|
102
|
+
out
|
103
|
+
end
|
104
|
+
|
105
|
+
# decolorize, remove newlines and truncate for table cell
|
106
|
+
def format_process_output(process, max_length=50, return_color=cyan)
|
107
|
+
out = ""
|
108
|
+
if process['output']
|
109
|
+
lines = process['output'].split("\n").collect {|line| reset + "#{line.to_s.strip}" }
|
110
|
+
out = lines.join(" ")
|
111
|
+
if max_length
|
112
|
+
out = truncate_string(out, max_length)
|
113
|
+
end
|
114
|
+
out << return_color if return_color
|
115
|
+
end
|
116
|
+
out
|
117
|
+
end
|
118
|
+
|
119
|
+
# format for either ETA/Duration
|
120
|
+
def format_process_duration(process, time_format="%H:%M:%S")
|
121
|
+
out = ""
|
122
|
+
if process['duration'] && process['duration'] > 0
|
123
|
+
out = format_duration_milliseconds(process['duration'], time_format)
|
124
|
+
elsif process['statusEta'] && process['statusEta'] > 0
|
125
|
+
out = format_duration_milliseconds(process['statusEta'], time_format)
|
126
|
+
elsif process['startDate'] && process['endDate']
|
127
|
+
out = format_duration(process['startDate'], process['endDate'], time_format)
|
128
|
+
else
|
129
|
+
""
|
130
|
+
end
|
131
|
+
out
|
132
|
+
end
|
133
|
+
|
134
|
+
end
|
@@ -126,20 +126,10 @@ module Morpheus
|
|
126
126
|
# I suppose the entered value should take precedence
|
127
127
|
# api_params = api_params.merge(options) # this might be good enough
|
128
128
|
# dup it
|
129
|
-
select_api_params = {}.merge(api_params || {})
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
v.each {|k2, v2|
|
134
|
-
select_api_params["#{k}.#{k2}"] = v2
|
135
|
-
}
|
136
|
-
else
|
137
|
-
# could be String/Symbol duplication issues here.
|
138
|
-
select_api_params.delete(k.to_sym)
|
139
|
-
select_api_params[k.to_s] = v
|
140
|
-
end
|
141
|
-
end
|
142
|
-
value = select_prompt(option_type,api_client, select_api_params)
|
129
|
+
select_api_params = {}.merge(api_params || {}).merge(results)
|
130
|
+
grails_select_api_params = grails_params(select_api_params)
|
131
|
+
|
132
|
+
value = select_prompt(option_type,api_client, grails_select_api_params)
|
143
133
|
elsif option_type['type'] == 'hidden'
|
144
134
|
value = option_type['defaultValue']
|
145
135
|
input = value
|
@@ -155,6 +145,21 @@ module Morpheus
|
|
155
145
|
return results
|
156
146
|
end
|
157
147
|
|
148
|
+
def self.grails_params(data, context=nil)
|
149
|
+
params = {}
|
150
|
+
data.each do |k,v|
|
151
|
+
if v.is_a?(Hash)
|
152
|
+
params.merge!(grails_params(v, context ? "#{context}.#{k.to_s}" : k))
|
153
|
+
else
|
154
|
+
if context
|
155
|
+
params["#{context}.#{k.to_s}"] = v
|
156
|
+
else
|
157
|
+
params[k.to_s] = v
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
return params
|
162
|
+
end
|
158
163
|
|
159
164
|
def self.radio_prompt(option_type)
|
160
165
|
value_found = false
|
@@ -368,7 +373,7 @@ module Morpheus
|
|
368
373
|
# value = input.empty? ? option_type['defaultValue'] : input
|
369
374
|
if input == '?' && value.nil?
|
370
375
|
help_prompt(option_type)
|
371
|
-
elsif
|
376
|
+
elsif input.chomp == 'EOF'
|
372
377
|
value_found = true
|
373
378
|
else
|
374
379
|
if value.nil?
|
@@ -377,7 +382,7 @@ module Morpheus
|
|
377
382
|
value << input + "\n"
|
378
383
|
end
|
379
384
|
end
|
380
|
-
return value
|
385
|
+
return value ? value.strip : value
|
381
386
|
end
|
382
387
|
|
383
388
|
def self.password_prompt(option_type)
|
@@ -1,14 +1,15 @@
|
|
1
1
|
require 'fileutils'
|
2
2
|
require 'morpheus/cli/cli_command'
|
3
3
|
require 'morpheus/cli/mixins/library_helper'
|
4
|
+
require 'morpheus/morpkg'
|
4
5
|
|
5
6
|
class Morpheus::Cli::LibraryPackagesCommand
|
6
7
|
include Morpheus::Cli::CliCommand
|
7
8
|
include Morpheus::Cli::LibraryHelper
|
8
9
|
|
9
10
|
set_command_name :'packages'
|
10
|
-
|
11
|
-
register_subcommands :
|
11
|
+
register_subcommands :list, :search, :get, :install, :update, :remove, :export
|
12
|
+
register_subcommands :'install-file' => :install_file
|
12
13
|
|
13
14
|
# hide until this is released
|
14
15
|
set_command_hidden
|
@@ -41,7 +42,7 @@ class Morpheus::Cli::LibraryPackagesCommand
|
|
41
42
|
|
42
43
|
if options[:dry_run]
|
43
44
|
print_dry_run @packages_interface.dry.list(params)
|
44
|
-
return
|
45
|
+
return 0
|
45
46
|
end
|
46
47
|
json_response = @packages_interface.list(params)
|
47
48
|
if options[:json]
|
@@ -69,7 +70,7 @@ class Morpheus::Cli::LibraryPackagesCommand
|
|
69
70
|
description: package['description'],
|
70
71
|
}
|
71
72
|
}
|
72
|
-
columns = [:code, :name, {:description => {:max_width => 50}}]
|
73
|
+
columns = [:code, :name, {:description => {:max_width => 50}}, :version]
|
73
74
|
# custom pretty table columns ...
|
74
75
|
if options[:include_fields]
|
75
76
|
columns = options[:include_fields]
|
@@ -98,11 +99,14 @@ class Morpheus::Cli::LibraryPackagesCommand
|
|
98
99
|
optparse.parse!(args)
|
99
100
|
connect(options)
|
100
101
|
begin
|
102
|
+
if args[0]
|
103
|
+
options[:phrase] = args[0]
|
104
|
+
# params['phrase'] = args[0]
|
105
|
+
end
|
101
106
|
params.merge!(parse_list_options(options))
|
102
|
-
|
103
107
|
if options[:dry_run]
|
104
108
|
print_dry_run @packages_interface.dry.search(params)
|
105
|
-
return
|
109
|
+
return 0
|
106
110
|
end
|
107
111
|
json_response = @packages_interface.search(params)
|
108
112
|
if options[:json]
|
@@ -126,11 +130,11 @@ class Morpheus::Cli::LibraryPackagesCommand
|
|
126
130
|
{
|
127
131
|
code: package['code'],
|
128
132
|
name: package['name'],
|
129
|
-
|
130
|
-
|
133
|
+
description: package['description'],
|
134
|
+
versions: (package['versions'] || []).collect {|v| v['version'] }.join(', ')
|
131
135
|
}
|
132
136
|
}
|
133
|
-
columns = [:code, :name, {:description => {:max_width => 50}}]
|
137
|
+
columns = [:code, :name, {:description => {:max_width => 50}}, {:versions => {:max_width => 30}}]
|
134
138
|
# custom pretty table columns ...
|
135
139
|
if options[:include_fields]
|
136
140
|
columns = options[:include_fields]
|
@@ -149,11 +153,279 @@ class Morpheus::Cli::LibraryPackagesCommand
|
|
149
153
|
end
|
150
154
|
|
151
155
|
def get(args)
|
152
|
-
|
156
|
+
options = {}
|
157
|
+
params = {}
|
158
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
159
|
+
opts.banner = subcommand_usage("[code]")
|
160
|
+
opts.on( nil, '--versions', "Display Available Versions" ) do
|
161
|
+
options[:show_versions] = true
|
162
|
+
end
|
163
|
+
opts.on( nil, '--objects', "Display Installed Package Objects" ) do
|
164
|
+
options[:show_objects] = true
|
165
|
+
end
|
166
|
+
build_common_options(opts, options, [:query, :json, :yaml, :csv, :fields, :dry_run, :remote])
|
167
|
+
opts.footer = "Get details about a package.\n" +
|
168
|
+
"[code] is required. This is the package code."
|
169
|
+
end
|
170
|
+
optparse.parse!(args)
|
171
|
+
connect(options)
|
172
|
+
if args.count != 1
|
173
|
+
print_error Morpheus::Terminal.angry_prompt
|
174
|
+
puts_error "#{command_name} get expects 1 argument and received #{args.count} #{args.join(' ')}\n#{optparse}"
|
175
|
+
return 1
|
176
|
+
end
|
177
|
+
begin
|
178
|
+
params.merge!(parse_list_options(options))
|
179
|
+
params['code'] = args[0]
|
180
|
+
if options[:dry_run]
|
181
|
+
print_dry_run @packages_interface.dry.info(params)
|
182
|
+
return 0
|
183
|
+
end
|
184
|
+
json_response = @packages_interface.info(params)
|
185
|
+
if options[:json]
|
186
|
+
puts as_json(json_response, options, "package")
|
187
|
+
return 0
|
188
|
+
elsif options[:yaml]
|
189
|
+
puts as_yaml(json_response, options, "package")
|
190
|
+
return 0
|
191
|
+
elsif options[:csv]
|
192
|
+
puts records_as_csv(json_response["package"], options)
|
193
|
+
else
|
194
|
+
installed_package = json_response["package"]
|
195
|
+
available_package = json_response["availablePackage"]
|
196
|
+
|
197
|
+
if installed_package.nil? && available_package.nil?
|
198
|
+
print yellow,"No package found for code '#{params['code']}'",reset,"\n"
|
199
|
+
return 1
|
200
|
+
end
|
201
|
+
|
202
|
+
print_h1 "Package Info"
|
203
|
+
|
204
|
+
# merge installed and available package info
|
205
|
+
package_record = installed_package || available_package
|
206
|
+
#package_record['versions'] = available_package['versions'] if available_package
|
207
|
+
print cyan
|
208
|
+
description_cols = {
|
209
|
+
# "Organization" => 'organization',
|
210
|
+
"Code" => 'code',
|
211
|
+
"Name" => 'name',
|
212
|
+
"Description" => 'description',
|
213
|
+
# "Type" => lambda {|it| (it['packageType'] || it['type']).to_s },
|
214
|
+
"Latest Version" => lambda {|it|
|
215
|
+
if available_package && available_package['versions']
|
216
|
+
latest_version = available_package['versions'].find {|v| v['latestVersion'] }
|
217
|
+
if latest_version.nil?
|
218
|
+
sorted_versions = available_package['versions'].sort {|x,y| x['dateCreated'] <=> y['dateCreated'] }
|
219
|
+
latest_version = sorted_versions.first()
|
220
|
+
end
|
221
|
+
latest_version ? latest_version['version'] : ""
|
222
|
+
else
|
223
|
+
""
|
224
|
+
end
|
225
|
+
},
|
226
|
+
"Installed Version" => lambda {|it|
|
227
|
+
installed_package ? installed_package['version'] : ""
|
228
|
+
},
|
229
|
+
"Status" => lambda {|it|
|
230
|
+
installed_package ? format_package_status(installed_package['status']) : format_package_status(nil)
|
231
|
+
},
|
232
|
+
}
|
233
|
+
if installed_package.nil?
|
234
|
+
description_cols.delete("Installed Version")
|
235
|
+
end
|
236
|
+
|
237
|
+
print_description_list(description_cols, package_record)
|
238
|
+
# print reset, "\n"
|
239
|
+
if options[:show_versions]
|
240
|
+
print_h2 "Available Versions"
|
241
|
+
if available_package.nil?
|
242
|
+
print yellow,"No marketplace package found for code '#{params['code']}'",reset,"\n"
|
243
|
+
else
|
244
|
+
if available_package['versions'].nil? || available_package['versions'].empty?
|
245
|
+
print yellow,"No available versions found",reset,"\n"
|
246
|
+
else
|
247
|
+
# api is sorting these with latestVersion first, right?
|
248
|
+
sorted_versions = available_package['versions'] || []
|
249
|
+
#sorted_versions = available_package['versions'].sort {|x,y| x['dateCreated'] <=> y['dateCreated'] }
|
250
|
+
rows = sorted_versions.collect {|package_version|
|
251
|
+
{
|
252
|
+
"PACKAGE VERSION": package_version['version'],
|
253
|
+
"MORPHEUS VERSION": package_version['minApplianceVersion'],
|
254
|
+
"PUBLISH DATE": format_local_dt(package_version['created'] || package_version['dateCreated'])
|
255
|
+
}
|
256
|
+
}
|
257
|
+
columns = ["PACKAGE VERSION", "MORPHEUS VERSION", "PUBLISH DATE"]
|
258
|
+
print cyan
|
259
|
+
print as_pretty_table(rows, columns)
|
260
|
+
# print reset, "\n"
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
264
|
+
if options[:show_objects]
|
265
|
+
print_h2 "Package Objects"
|
266
|
+
if installed_package.nil?
|
267
|
+
print cyan,"No objects to show",reset,"\n"
|
268
|
+
else
|
269
|
+
if installed_package['objects'].nil? || installed_package['objects'].empty?
|
270
|
+
print yellow,"No objects to show",reset,"\n"
|
271
|
+
else
|
272
|
+
rows = installed_package['objects'].collect {|it|
|
273
|
+
{
|
274
|
+
type: it['refType'],
|
275
|
+
id: it['refId'],
|
276
|
+
name: it['displayName']
|
277
|
+
}
|
278
|
+
}
|
279
|
+
columns = [:type, :id, :name]
|
280
|
+
print cyan
|
281
|
+
print as_pretty_table(rows, columns)
|
282
|
+
# print reset, "\n"
|
283
|
+
end
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
print reset, "\n"
|
288
|
+
return 0
|
289
|
+
end
|
290
|
+
rescue RestClient::Exception => e
|
291
|
+
print_rest_exception(e, options)
|
292
|
+
exit 1
|
293
|
+
end
|
153
294
|
end
|
154
295
|
|
155
296
|
def install(args)
|
156
|
-
|
297
|
+
full_command_string = "#{command_name} install #{args.join(' ')}".strip
|
298
|
+
options = {}
|
299
|
+
params = {}
|
300
|
+
open_prog = nil
|
301
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
302
|
+
opts.banner = subcommand_usage("[code]")
|
303
|
+
opts.on('-v','--version VERSION', "Package Version number") do |val|
|
304
|
+
params['version'] = val
|
305
|
+
end
|
306
|
+
# opts.on('--package-version VALUE', String, "Version number for package.") do |val|
|
307
|
+
# params['version'] = val
|
308
|
+
# end
|
309
|
+
opts.on('--organization NAME', String, "Package Organization. Default is morpheus.") do |val|
|
310
|
+
params['organization'] = val
|
311
|
+
end
|
312
|
+
opts.on( '-f', '--force', "Force Install." ) do
|
313
|
+
params['force'] = true
|
314
|
+
end
|
315
|
+
build_common_options(opts, options, [:options, :json, :dry_run, :remote, :quiet])
|
316
|
+
opts.footer = "Install a morpheus package from the marketplace.\n" +
|
317
|
+
"[code] is required. This is the package code."
|
318
|
+
end
|
319
|
+
optparse.parse!(args)
|
320
|
+
if args.count != 1
|
321
|
+
print_error Morpheus::Terminal.angry_prompt
|
322
|
+
puts_error "#{command_name} install expects 1 argument and received #{args.count} #{args.join(' ')}\n#{optparse}"
|
323
|
+
return 1
|
324
|
+
end
|
325
|
+
connect(options)
|
326
|
+
begin
|
327
|
+
params['code'] = args[0]
|
328
|
+
if options[:dry_run]
|
329
|
+
#print cyan,bold, " - Uploading #{local_file_path} to #{bucket_id}:#{destination} DRY RUN", reset, "\n"
|
330
|
+
# print_h1 "DRY RUN"
|
331
|
+
print_dry_run @packages_interface.dry.install(params)
|
332
|
+
print "\n"
|
333
|
+
return 0
|
334
|
+
end
|
335
|
+
|
336
|
+
json_response = @packages_interface.install(params)
|
337
|
+
if options[:json]
|
338
|
+
puts as_json(json_response, options)
|
339
|
+
elsif !options[:quiet]
|
340
|
+
package_str = params['code'] || ""
|
341
|
+
installed_package = json_response['package']
|
342
|
+
if installed_package && installed_package['code']
|
343
|
+
package_str = installed_package['code']
|
344
|
+
end
|
345
|
+
if installed_package && installed_package['version']
|
346
|
+
package_str = "#{package_str} #{installed_package['version']}"
|
347
|
+
end
|
348
|
+
print_green_success "Installed package #{package_str}"
|
349
|
+
end
|
350
|
+
|
351
|
+
rescue RestClient::Exception => e
|
352
|
+
print_rest_exception(e, options)
|
353
|
+
return 1
|
354
|
+
end
|
355
|
+
end
|
356
|
+
|
357
|
+
def install_file(args)
|
358
|
+
full_command_string = "#{command_name} install-file #{args.join(' ')}".strip
|
359
|
+
options = {}
|
360
|
+
params = {}
|
361
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
362
|
+
opts.banner = subcommand_usage("[morpkg-file]")
|
363
|
+
# opts.on('--url URL', String, "Use a remote URL as the source .morpkg instead of uploading a file.") do |val|
|
364
|
+
# params['url'] = val
|
365
|
+
# end
|
366
|
+
opts.on( '-f', '--force', "Force Install." ) do
|
367
|
+
params['force'] = true
|
368
|
+
end
|
369
|
+
build_common_options(opts, options, [:options, :dry_run, :quiet])
|
370
|
+
opts.footer = "Install a morpheus package with a .morpkg file directly.\n" +
|
371
|
+
"[morpkg-file] is required. This is the local filepath of a .morpkg to be uploaded and installed."
|
372
|
+
end
|
373
|
+
optparse.parse!(args)
|
374
|
+
|
375
|
+
if args.count != 1
|
376
|
+
print_error Morpheus::Terminal.angry_prompt
|
377
|
+
puts_error "expects 1 argument and received #{args.count} #{args.join(' ')}\n#{optparse}"
|
378
|
+
return 1
|
379
|
+
end
|
380
|
+
|
381
|
+
# validate local file path
|
382
|
+
local_file_path = File.expand_path(args[0].squeeze('/'))
|
383
|
+
if local_file_path == "" || local_file_path == "/" || local_file_path == "."
|
384
|
+
print_error Morpheus::Terminal.angry_prompt
|
385
|
+
puts_error "bad argument: [morpkg-file]\nFile '#{local_file_path}' is invalid.\n#{optparse}"
|
386
|
+
return 1
|
387
|
+
end
|
388
|
+
if !File.exists?(local_file_path)
|
389
|
+
print_error Morpheus::Terminal.angry_prompt
|
390
|
+
puts_error "bad argument: [morpkg-file]\nFile '#{local_file_path}' was not found.\n"
|
391
|
+
return 1
|
392
|
+
end
|
393
|
+
if !File.file?(local_file_path)
|
394
|
+
print_error Morpheus::Terminal.angry_prompt
|
395
|
+
puts_error "bad argument: [morpkg-file]\nFile '#{local_file_path}' is not a file.\n"
|
396
|
+
return 1
|
397
|
+
end
|
398
|
+
|
399
|
+
connect(options)
|
400
|
+
begin
|
401
|
+
|
402
|
+
if options[:dry_run]
|
403
|
+
#print cyan,bold, " - Uploading #{local_file_path} to #{bucket_id}:#{destination} DRY RUN", reset, "\n"
|
404
|
+
# print_h1 "DRY RUN"
|
405
|
+
print_dry_run @packages_interface.dry.install_file(local_file_path, params)
|
406
|
+
print "\n"
|
407
|
+
return 0
|
408
|
+
end
|
409
|
+
|
410
|
+
json_response = @packages_interface.install_file(local_file_path, params)
|
411
|
+
if options[:json]
|
412
|
+
puts as_json(json_response, options)
|
413
|
+
elsif !options[:quiet]
|
414
|
+
package_str = params['code'] || ""
|
415
|
+
installed_package = json_response['package']
|
416
|
+
if installed_package && installed_package['code']
|
417
|
+
package_str = installed_package['code']
|
418
|
+
end
|
419
|
+
if installed_package && installed_package['version']
|
420
|
+
package_str = "#{package_str} #{installed_package['version']}"
|
421
|
+
end
|
422
|
+
print_green_success "Installed package #{package_str}"
|
423
|
+
end
|
424
|
+
|
425
|
+
rescue RestClient::Exception => e
|
426
|
+
print_rest_exception(e, options)
|
427
|
+
return 1
|
428
|
+
end
|
157
429
|
end
|
158
430
|
|
159
431
|
def update(args)
|
@@ -164,7 +436,7 @@ class Morpheus::Cli::LibraryPackagesCommand
|
|
164
436
|
raise "not yet implemented"
|
165
437
|
end
|
166
438
|
|
167
|
-
#
|
439
|
+
# download a new .morpkg for remote instanceType(s)
|
168
440
|
def export(args)
|
169
441
|
full_command_string = "#{command_name} export #{args.join(' ')}".strip
|
170
442
|
options = {}
|
@@ -241,15 +513,15 @@ class Morpheus::Cli::LibraryPackagesCommand
|
|
241
513
|
end
|
242
514
|
end
|
243
515
|
build_common_options(opts, options, [:options, :dry_run, :quiet])
|
244
|
-
opts.footer = "Export one or many instance types as a morpheus
|
516
|
+
opts.footer = "Export one or many instance types as a morpheus package (.morpkg) file.\n" +
|
245
517
|
"[instance-type] is required. This is the instance type code." +
|
246
|
-
"--instance-types can
|
518
|
+
"--instance-types can be export multiple instance types at once. This is a list of instance type codes."
|
247
519
|
end
|
248
520
|
optparse.parse!(args)
|
249
521
|
|
250
522
|
if args.count != 1 && !instance_type_codes && !params['all']
|
251
523
|
print_error Morpheus::Terminal.angry_prompt
|
252
|
-
puts_error "#{command_name} export expects 1 argument and received #{args.count}
|
524
|
+
puts_error "#{command_name} export expects 1 argument and received #{args.count} #{args.join(' ')}\n#{optparse}"
|
253
525
|
return 1
|
254
526
|
end
|
255
527
|
connect(options)
|
@@ -308,7 +580,7 @@ class Morpheus::Cli::LibraryPackagesCommand
|
|
308
580
|
|
309
581
|
if options[:dry_run]
|
310
582
|
print_dry_run @packages_interface.dry.export(params, outfile), full_command_string
|
311
|
-
return
|
583
|
+
return 0
|
312
584
|
end
|
313
585
|
if !options[:quiet]
|
314
586
|
print cyan + "Downloading morpheus package file #{outfile} ... "
|
@@ -362,8 +634,188 @@ class Morpheus::Cli::LibraryPackagesCommand
|
|
362
634
|
end
|
363
635
|
end
|
364
636
|
|
637
|
+
# generate a new .morpkg for a local source directory
|
638
|
+
def build(args)
|
639
|
+
full_command_string = "#{command_name} build #{args.join(' ')}".strip
|
640
|
+
options = {}
|
641
|
+
source_directory = nil
|
642
|
+
outfile = nil
|
643
|
+
do_overwrite = false
|
644
|
+
do_mkdir = false
|
645
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
646
|
+
opts.banner = subcommand_usage("[source] [target]")
|
647
|
+
opts.on('--source FILE', String, "Source directory of the package being built.") do |val|
|
648
|
+
source_directory = val
|
649
|
+
end
|
650
|
+
opts.on('--target FILE', String, "Destination filename for the .morpkg output file. Default is [code]-[version].morpkg") do |val|
|
651
|
+
outfile = val
|
652
|
+
end
|
653
|
+
opts.on( '-p', '--mkdir', "Create missing directories for [target] if they do not exist." ) do
|
654
|
+
do_mkdir = true
|
655
|
+
end
|
656
|
+
opts.on( '-f', '--force', "Overwrite existing [target] if it exists. Also creates missing directories." ) do
|
657
|
+
do_overwrite = true
|
658
|
+
do_mkdir = true
|
659
|
+
end
|
660
|
+
|
661
|
+
build_common_options(opts, options, [:options, :dry_run, :quiet])
|
662
|
+
opts.footer = "Generate a new morpheus package file. \n" +
|
663
|
+
"[source] is required. This is the source directory of the package.\n" +
|
664
|
+
"[target] is the output filename. The default is [code]-[version].morpkg"
|
665
|
+
end
|
666
|
+
optparse.parse!(args)
|
667
|
+
|
668
|
+
if args.count < 1 || args.count > 2
|
669
|
+
print_error Morpheus::Terminal.angry_prompt
|
670
|
+
puts_error "wrong number of arguments, expected 1-2 and got #{args.count}\n#{optparse}"
|
671
|
+
return 1
|
672
|
+
end
|
673
|
+
if args[0]
|
674
|
+
source_directory = args[0]
|
675
|
+
end
|
676
|
+
if args[1]
|
677
|
+
outfile = args[1]
|
678
|
+
end
|
679
|
+
if !source_directory
|
680
|
+
print_error Morpheus::Terminal.angry_prompt
|
681
|
+
puts_error "missing required argument [source]\n#{optparse}"
|
682
|
+
return 1
|
683
|
+
end
|
684
|
+
|
685
|
+
# connect(options)
|
686
|
+
begin
|
687
|
+
# validate source
|
688
|
+
source_directory = File.expand_path(source_directory)
|
689
|
+
if !File.exists?(source_directory)
|
690
|
+
puts_error "#{Morpheus::Terminal.angry_prompt}[source] is invalid. Directory not found: #{source_directory}"
|
691
|
+
return 1
|
692
|
+
end
|
693
|
+
if !File.directory?(source_directory)
|
694
|
+
puts_error "#{Morpheus::Terminal.angry_prompt}[source] is invalid. Not a directory: #{source_directory}"
|
695
|
+
return 1
|
696
|
+
end
|
697
|
+
# parse package source
|
698
|
+
|
699
|
+
package_org = nil
|
700
|
+
package_code = nil
|
701
|
+
package_version = nil
|
702
|
+
package_type = nil
|
703
|
+
|
704
|
+
|
705
|
+
# validate package
|
706
|
+
if !package_code.nil? || package_code.empty?
|
707
|
+
puts_error "#{Morpheus::Terminal.angry_prompt}package data is invalid. Missing package code."
|
708
|
+
return 1
|
709
|
+
end
|
710
|
+
if package_version.nil? || package_version.empty?
|
711
|
+
puts_error "#{Morpheus::Terminal.angry_prompt}package data is invalid. Missing package version."
|
712
|
+
return 1
|
713
|
+
end
|
714
|
+
# determine outfile
|
715
|
+
if !outfile
|
716
|
+
outfile = File.join(File.dirname(source_directory), "#{package_code}-#{package_version}.morpkg")
|
717
|
+
else
|
718
|
+
outfile = File.expand_path(outfile)
|
719
|
+
end
|
720
|
+
if Dir.exists?(outfile)
|
721
|
+
puts_error "#{Morpheus::Terminal.angry_prompt}[target] is invalid. It is the name of an existing directory: #{outfile}"
|
722
|
+
return 1
|
723
|
+
end
|
724
|
+
# always a .morpkg
|
725
|
+
if outfile[-7..-1] != ".morpkg"
|
726
|
+
outfile << ".morpkg"
|
727
|
+
end
|
728
|
+
destination_dir = File.dirname(outfile)
|
729
|
+
if !Dir.exists?(destination_dir)
|
730
|
+
if do_mkdir
|
731
|
+
print cyan,"Creating local directory #{destination_dir}",reset,"\n"
|
732
|
+
FileUtils.mkdir_p(destination_dir)
|
733
|
+
else
|
734
|
+
puts_error "#{Morpheus::Terminal.angry_prompt}[target] is invalid. Directory not found: #{destination_dir} Use -p to create the missing directory."
|
735
|
+
return 1
|
736
|
+
end
|
737
|
+
end
|
738
|
+
if File.exists?(outfile)
|
739
|
+
if do_overwrite
|
740
|
+
# uhh need to be careful wih the passed filepath here..
|
741
|
+
# don't delete, just overwrite.
|
742
|
+
# File.delete(outfile)
|
743
|
+
else
|
744
|
+
puts_error "#{Morpheus::Terminal.angry_prompt}[target] is invalid. File already exists: #{outfile}", "Use -f to overwrite the existing file."
|
745
|
+
return 1
|
746
|
+
end
|
747
|
+
end
|
748
|
+
|
749
|
+
if options[:dry_run]
|
750
|
+
print cyan + "Building morpheus package at #{source_directory} ..."
|
751
|
+
print cyan + "DRY RUN" + reset + "\n"
|
752
|
+
return 0
|
753
|
+
end
|
754
|
+
if !options[:quiet]
|
755
|
+
print cyan + "Building morpheus package at #{source_directory} ... "
|
756
|
+
end
|
757
|
+
|
758
|
+
# build it
|
759
|
+
#http_response, bad_body = @packages_interface.export(params, outfile)
|
760
|
+
|
761
|
+
# FileUtils.chmod(0600, outfile)
|
762
|
+
success = true
|
763
|
+
error_msg = nil
|
764
|
+
build_outfile = nil
|
765
|
+
begin
|
766
|
+
build_outfile = Morpheus::Morpkg.build_package(source_directory, outfile, options[:force])
|
767
|
+
success = true
|
768
|
+
rescue => ex
|
769
|
+
error_msg = ex.message
|
770
|
+
end
|
771
|
+
if success
|
772
|
+
if !options[:quiet]
|
773
|
+
print green + "SUCCESS" + reset + "\n"
|
774
|
+
print green + "Generated #{build_outfile}" + reset + "\n"
|
775
|
+
end
|
776
|
+
return 0
|
777
|
+
else
|
778
|
+
if !options[:quiet]
|
779
|
+
print red + "ERROR" + reset + "\n"
|
780
|
+
if error_msg
|
781
|
+
print_error red + error_msg + reset + "\n"
|
782
|
+
end
|
783
|
+
end
|
784
|
+
# F it, just remove a bad result
|
785
|
+
# if File.exists?(outfile) && File.file?(outfile)
|
786
|
+
# Morpheus::Logging::DarkPrinter.puts "Deleting bad build file: #{outfile}" if Morpheus::Logging.debug?
|
787
|
+
# File.delete(outfile)
|
788
|
+
# end
|
789
|
+
if options[:debug]
|
790
|
+
# puts_error error_msg
|
791
|
+
end
|
792
|
+
return 1
|
793
|
+
end
|
794
|
+
|
795
|
+
rescue RestClient::Exception => e
|
796
|
+
print_rest_exception(e, options)
|
797
|
+
return 1
|
798
|
+
end
|
799
|
+
end
|
800
|
+
|
365
801
|
private
|
366
802
|
|
367
|
-
|
803
|
+
def format_package_status(status_string, return_color=cyan)
|
804
|
+
out = ""
|
805
|
+
if status_string == 'installed'
|
806
|
+
out << "#{green}#{status_string.upcase}#{return_color}"
|
807
|
+
elsif status_string == 'uninstalled' || status_string == 'removed'
|
808
|
+
out << "#{red}#{status_string.upcase}#{return_color}"
|
809
|
+
elsif status_string == 'installing'
|
810
|
+
out << "#{cyan}#{status_string.upcase}#{return_color}"
|
811
|
+
elsif status_string.nil?
|
812
|
+
out << "#{cyan}NOT INSTALLED#{return_color}"
|
813
|
+
elsif !status_string.nil?
|
814
|
+
out << "#{yellow}#{status_string.upcase}#{return_color}"
|
815
|
+
else
|
816
|
+
out << ""
|
817
|
+
end
|
818
|
+
out
|
819
|
+
end
|
368
820
|
|
369
821
|
end
|