morpheus-cli 5.5.2.1 → 5.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/.gitignore +2 -1
- data/Dockerfile +1 -1
- data/README.md +57 -4
- data/Rakefile +9 -0
- data/bin/morpheus +4 -4
- data/lib/morpheus/api/api_client.rb +8 -2
- data/lib/morpheus/api/archive_buckets_interface.rb +1 -1
- data/lib/morpheus/api/archive_files_interface.rb +3 -3
- data/lib/morpheus/api/clients_interface.rb +2 -2
- data/lib/morpheus/api/clusters_interface.rb +8 -1
- data/lib/morpheus/api/containers_interface.rb +29 -16
- data/lib/morpheus/api/custom_instance_types_interface.rb +0 -2
- data/lib/morpheus/api/doc_interface.rb +8 -6
- data/lib/morpheus/api/file_copy_request_interface.rb +1 -1
- data/lib/morpheus/api/health_interface.rb +1 -1
- data/lib/morpheus/api/image_builder_interface.rb +3 -3
- data/lib/morpheus/api/instances_interface.rb +25 -0
- data/lib/morpheus/api/logs_interface.rb +2 -4
- data/lib/morpheus/api/monitoring_interface.rb +6 -6
- data/lib/morpheus/api/packages_interface.rb +1 -1
- data/lib/morpheus/api/reports_interface.rb +1 -1
- data/lib/morpheus/api/servers_interface.rb +9 -1
- data/lib/morpheus/api/storage_providers_interface.rb +2 -2
- data/lib/morpheus/api/virtual_images_interface.rb +1 -1
- data/lib/morpheus/api.rb +2 -0
- data/lib/morpheus/benchmarking.rb +1 -1
- data/lib/morpheus/cli/cli_command.rb +69 -36
- data/lib/morpheus/cli/cli_registry.rb +19 -10
- data/lib/morpheus/cli/commands/access_token_command.rb +1 -1
- data/lib/morpheus/cli/commands/apps.rb +1 -1
- data/lib/morpheus/cli/commands/archives_command.rb +25 -33
- data/lib/morpheus/cli/commands/blueprints_command.rb +10 -21
- data/lib/morpheus/cli/commands/boot_scripts_command.rb +2 -2
- data/lib/morpheus/cli/commands/cat_command.rb +1 -1
- data/lib/morpheus/cli/commands/catalog_item_types_command.rb +12 -12
- data/lib/morpheus/cli/commands/clouds.rb +3 -3
- data/lib/morpheus/cli/commands/clusters.rb +154 -3
- data/lib/morpheus/cli/commands/containers_command.rb +398 -253
- data/lib/morpheus/cli/commands/deployments.rb +1 -1
- data/lib/morpheus/cli/commands/deploys.rb +9 -9
- data/lib/morpheus/cli/commands/doc.rb +15 -16
- data/lib/morpheus/cli/commands/execution_request_command.rb +2 -2
- data/lib/morpheus/cli/commands/file_copy_request_command.rb +5 -5
- data/lib/morpheus/cli/commands/groups.rb +2 -2
- data/lib/morpheus/cli/commands/health_command.rb +4 -4
- data/lib/morpheus/cli/commands/hosts.rb +43 -5
- data/lib/morpheus/cli/commands/image_builder_command.rb +1 -1
- data/lib/morpheus/cli/commands/instances.rb +419 -148
- data/lib/morpheus/cli/commands/integrations_command.rb +22 -20
- data/lib/morpheus/cli/commands/key_pairs.rb +2 -2
- data/lib/morpheus/cli/commands/library_container_scripts_command.rb +2 -2
- data/lib/morpheus/cli/commands/library_container_templates_command.rb +2 -2
- data/lib/morpheus/cli/commands/library_instance_types_command.rb +3 -3
- data/lib/morpheus/cli/commands/library_spec_templates_command.rb +2 -2
- data/lib/morpheus/cli/commands/login.rb +1 -1
- data/lib/morpheus/cli/commands/man_command.rb +32 -18
- data/lib/morpheus/cli/commands/packages_command.rb +11 -11
- data/lib/morpheus/cli/commands/plugins.rb +1 -1
- data/lib/morpheus/cli/commands/policies_command.rb +4 -4
- data/lib/morpheus/cli/commands/preseed_scripts_command.rb +2 -2
- data/lib/morpheus/cli/commands/remote.rb +1 -1
- data/lib/morpheus/cli/commands/reports_command.rb +3 -3
- data/lib/morpheus/cli/commands/roles.rb +6 -3
- data/lib/morpheus/cli/commands/security_groups.rb +1 -1
- data/lib/morpheus/cli/commands/shell.rb +40 -62
- data/lib/morpheus/cli/commands/snapshots.rb +3 -5
- data/lib/morpheus/cli/commands/source_command.rb +8 -16
- data/lib/morpheus/cli/commands/storage_providers_command.rb +7 -7
- data/lib/morpheus/cli/commands/tasks.rb +2 -2
- data/lib/morpheus/cli/commands/vdi_pools_command.rb +6 -6
- data/lib/morpheus/cli/commands/view.rb +5 -1
- data/lib/morpheus/cli/commands/whitelabel_settings_command.rb +4 -4
- data/lib/morpheus/cli/commands/whoami.rb +2 -2
- data/lib/morpheus/cli/credentials.rb +30 -8
- data/lib/morpheus/cli/dot_file.rb +8 -15
- data/lib/morpheus/cli/error_handler.rb +16 -0
- data/lib/morpheus/cli/errors.rb +8 -1
- data/lib/morpheus/cli/mixins/print_helper.rb +17 -13
- data/lib/morpheus/cli/mixins/rest_command.rb +18 -18
- data/lib/morpheus/cli/mixins/secondary_rest_command.rb +12 -12
- data/lib/morpheus/cli/option_parser.rb +5 -1
- data/lib/morpheus/cli/option_types.rb +59 -12
- data/lib/morpheus/cli/version.rb +1 -1
- data/lib/morpheus/cli.rb +26 -16
- data/lib/morpheus/ext/rest_client.rb +3 -2
- data/lib/morpheus/formatters.rb +1 -1
- data/lib/morpheus/logging.rb +4 -4
- data/lib/morpheus/morpkg.rb +4 -4
- data/lib/morpheus/rest_client.rb +2 -2
- data/lib/morpheus/routes.rb +2 -2
- data/lib/morpheus/terminal.rb +65 -16
- data/lib/morpheus.rb +1 -1
- data/morpheus-cli.gemspec +1 -0
- data/test/api/containers_interface_test.rb +68 -0
- data/test/api/doc_interface_test.rb +35 -0
- data/test/api/instances_interface_test.rb +22 -0
- data/test/api/whoami_interface_test.rb +14 -0
- data/test/cli/access_token_test.rb +36 -0
- data/test/cli/auth_test.rb +82 -0
- data/test/cli/cli_test.rb +48 -0
- data/test/cli/containers_test.rb +92 -0
- data/test/cli/doc_test.rb +35 -0
- data/test/cli/help_test.rb +25 -0
- data/test/cli/instances_test.rb +36 -0
- data/test/cli/man_test.rb +14 -0
- data/test/cli/remote_test.rb +89 -0
- data/test/cli/roles_test.rb +34 -0
- data/test/cli/shell_test.rb +81 -0
- data/test/cli/version_test.rb +23 -0
- data/test/cli/view_test.rb +55 -0
- data/test/cli/whoami_test.rb +17 -0
- data/test/morpheus_test.rb +16 -0
- data/test/test_case.rb +338 -0
- data/test/test_config.rb +137 -0
- data/test/test_data_helper.rb +97 -0
- metadata +61 -3
data/test/test_case.rb
ADDED
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
require 'test/unit'
|
|
2
|
+
# use a different default home directory when running tests
|
|
3
|
+
ENV['MORPHEUS_CLI_HOME'] = ENV['TEST_CLI_HOME'] || File.join(Dir.home, ".morpheus_test")
|
|
4
|
+
require 'morpheus'
|
|
5
|
+
require 'test_config'
|
|
6
|
+
#require 'securerandom'
|
|
7
|
+
|
|
8
|
+
module MorpheusTest
|
|
9
|
+
|
|
10
|
+
# TestCase is the base class for all unit tests to provide standard behavior
|
|
11
|
+
# for testing CLI commands and API interfaces.
|
|
12
|
+
#
|
|
13
|
+
# +assert_execute+ is available to execute a CLI command or expression and assert the result is a success (exit:0) by default.
|
|
14
|
+
#
|
|
15
|
+
# +terminal+ is available to execute any commands eg. +terminal.execute("source foo")+
|
|
16
|
+
#
|
|
17
|
+
# +client+ is available for executing API requests
|
|
18
|
+
#
|
|
19
|
+
class TestCase < Test::Unit::TestCase
|
|
20
|
+
include Morpheus::Cli::PrintHelper # printhelper for print_red_alert and what not.. should probably not be included in MorpheusTest
|
|
21
|
+
|
|
22
|
+
# execute test cases in the order they are defined.
|
|
23
|
+
self.test_order = :defined
|
|
24
|
+
|
|
25
|
+
# indicates the test requires the user to have a current remote
|
|
26
|
+
# override this to return false if your tests do not require `remote use` as part of its setup
|
|
27
|
+
def requires_remote
|
|
28
|
+
true
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# indicates the test requires the user to be logged in and authenticated
|
|
32
|
+
# override this to return false if your tests do not require `login` as part of its setup
|
|
33
|
+
def requires_authentication
|
|
34
|
+
true
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# hook at the beginning of each test
|
|
38
|
+
def setup()
|
|
39
|
+
#puts "TestCase #{self} setup()"
|
|
40
|
+
# @config is provided for accessing test environment settings in our tests
|
|
41
|
+
@config = get_config()
|
|
42
|
+
# use the remote and login if needed
|
|
43
|
+
use_remote() if requires_remote
|
|
44
|
+
login_if_needed() if requires_authentication
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# hook at the end of each test
|
|
48
|
+
def teardown()
|
|
49
|
+
#puts "TestCase #{self} teardown()"
|
|
50
|
+
#logout_if_needed() if requires_authentication
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# def initialize(*args)
|
|
54
|
+
# obj = super(*args)
|
|
55
|
+
# @client = establish_test_terminal()
|
|
56
|
+
# end
|
|
57
|
+
|
|
58
|
+
protected
|
|
59
|
+
|
|
60
|
+
# @return [Morpheus::APIClient] client for executing api requests in our tests and examining the results
|
|
61
|
+
def client
|
|
62
|
+
# todo: return terminal.get_api_client()
|
|
63
|
+
#@api_client ||= Morpheus::APIClient.new(url: @config.url, username: @config.username, password: @config.password_decrypted, verify_ssl: false, client_id: 'morph-api')
|
|
64
|
+
#@api_client.login() unless @api_client.logged_in?
|
|
65
|
+
# this only works while logged in, fine for now...
|
|
66
|
+
@client ||= Morpheus::APIClient.new(url: @config.url, username: @config.username, password: @config.password, access_token: get_access_token(), verify_ssl: false, client_id: 'morph-cli')
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
# Execute the command in the CLI terminal and assert the result
|
|
72
|
+
# By default, the result should be a success with exit code: 0 and error: nil
|
|
73
|
+
# @param [String] cmd the command or expression to be executed by the CLI terminal
|
|
74
|
+
# @param [Hash,String] opts. See +assert_command_result+ If passed as a String, then treated as failure message.
|
|
75
|
+
# @param [String] failure_message Optional message to use on failure used instead of the default "Expected command..."
|
|
76
|
+
#
|
|
77
|
+
# @example Assert a command succeeds with a 0 exit status
|
|
78
|
+
# assert_execute("whoami")
|
|
79
|
+
#
|
|
80
|
+
# @example Assert that a command fails with any non zero exit status
|
|
81
|
+
# assert_execute("foobar", success:false, "Expected unknown command to fail")
|
|
82
|
+
#
|
|
83
|
+
# @example Assert a specific exit status code
|
|
84
|
+
# assert_execute("foobar", exit:1, "Expected unknown command to fail")
|
|
85
|
+
#
|
|
86
|
+
def assert_execute(cmd, opts = {}, failure_message = nil)
|
|
87
|
+
opts = opts.is_a?(String) ? {failure_message:opts} : (opts || {})
|
|
88
|
+
result = terminal.execute(cmd)
|
|
89
|
+
assert_command_result(result, opts, failure_message)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# +assert_success()+ is an alias for +assert_execute()+
|
|
93
|
+
alias :assert_success :assert_execute
|
|
94
|
+
|
|
95
|
+
# Execute the command in the CLI terminal and assert that the result is in an error (exit non-0)
|
|
96
|
+
# @param [String] cmd the command or expression to be executed by the CLI terminal
|
|
97
|
+
# @param [Hash,String] opts. See +assert_command_result+ If passed as a String, then treated as failure message.
|
|
98
|
+
# @param [String] failure_message Optional message to use on failure used instead of the default "Expected command to succeed|fail..."
|
|
99
|
+
#
|
|
100
|
+
# @example Assert a command fails with an error
|
|
101
|
+
# assert_error("apps list --unknown-option", "Expected unknown option error")
|
|
102
|
+
#
|
|
103
|
+
def assert_error(cmd, opts = {}, failure_message = nil)
|
|
104
|
+
opts = opts.is_a?(String) ? {failure_message:opts} : (opts || {})
|
|
105
|
+
assert_execute(cmd, opts.merge(success:false), failure_message)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
# Assert that a command is has the expected exit status and message
|
|
110
|
+
# The default behavior is to assert the result is successful, exit: 0, error: nil
|
|
111
|
+
# use {success:false} to assert non-zero
|
|
112
|
+
# or use {exit:1, error: "something bad happened"} to assert a specific error exit and/or message
|
|
113
|
+
# @param [Array] Result containing: [exit_code, error].
|
|
114
|
+
# @param [Hash,String] opts. Hash of options for assertion. If passed as a String, then treated as failure message.
|
|
115
|
+
# @option opts [true,false] :success Expect success (true) or failure (false). Default is +true+, set to +false+ to assert that an error occurs instead.
|
|
116
|
+
# @option opts [Integer] :exit Expect a specific exit status code. The default is 0 on success or anything but 0 for failure.
|
|
117
|
+
# @option opts [String] :failure Optional message to use on failure used instead of the default "Expected command to succe..."
|
|
118
|
+
# @param [String] failure_message Optional message to use on failure used instead of the default "Expected command to succeed|fail..."
|
|
119
|
+
def assert_command_result(result, opts = {}, failure_message = nil)
|
|
120
|
+
# determine what to assert
|
|
121
|
+
success = opts.key?(:success) ? opts[:success] : true
|
|
122
|
+
expected_code = opts[:exit] ? opts[:exit] : (success ? 0 : nil)
|
|
123
|
+
if expected_code && expected_code != 0
|
|
124
|
+
success = false
|
|
125
|
+
end
|
|
126
|
+
expected_message = opts[:error]
|
|
127
|
+
failure_message = failure_message || opts[:failure_message] || opts[:failure]
|
|
128
|
+
# parse command result
|
|
129
|
+
exit_code, err = Morpheus::Cli::CliRegistry.parse_command_result(result)
|
|
130
|
+
# result assertions
|
|
131
|
+
if success
|
|
132
|
+
assert_equal([0, nil], [exit_code, err], failure_message || "Expected command to succeed with exit code: 0 and instead got exit code: #{exit_code.inspect}, error: #{err.inspect}")
|
|
133
|
+
else
|
|
134
|
+
if expected_code
|
|
135
|
+
assert_equal(expected_code, exit_code, failure_message || "Expected command to fail with exit code: #{expected_code.inspect} and instead got exit code: #{exit_code.inspect}, error: #{err.inspect}")
|
|
136
|
+
else
|
|
137
|
+
assert_not_equal(0, exit_code, failure_message || "Expected command to fail with exit code: (non-zero) and instead got exit code: #{exit_code.inspect}, error: #{err.inspect}")
|
|
138
|
+
end
|
|
139
|
+
if expected_message
|
|
140
|
+
assert_equal(expected_message, err, "Expected command to fail with error message: #{expected_message.inspect} and instead got error message: #{err.inspect}")
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Get the shared CLI terminal
|
|
146
|
+
# @return [Morpheus::Terminal]
|
|
147
|
+
def terminal
|
|
148
|
+
# return Morpheus::Terminal.instance
|
|
149
|
+
establish_test_terminal()
|
|
150
|
+
Thread.current[:terminal]
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Get the shared test configuration settings
|
|
154
|
+
# parses environment variables and config file to establish CLI environment
|
|
155
|
+
# @return [Map] containing :url, :username, etc., including :password!
|
|
156
|
+
def get_config(refresh=false)
|
|
157
|
+
Thread.current[:config] ||= TestConfig.new
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# todo: make this work IOError unitialized stream right now
|
|
161
|
+
QUIET = ENV['QUIET'] ? true : false unless defined?(QUIET)
|
|
162
|
+
|
|
163
|
+
# Creates a remote for executing unit tests
|
|
164
|
+
# This requires the user to set environment variables: TEST_URL, TEST_USERNAME and TEST_PASSWORD or TEST_CONFIG
|
|
165
|
+
# By default, test_config.yaml in the current directory can be used.
|
|
166
|
+
# The home directory can be specified with TEST_HOME, and the default TEST_CONFIG filename is ~/.morpheus_test
|
|
167
|
+
# returns the Morpheus::Terminal instance for executing CLI commands
|
|
168
|
+
def establish_test_terminal()
|
|
169
|
+
if Thread.current[:terminal].nil?
|
|
170
|
+
config = get_config()
|
|
171
|
+
|
|
172
|
+
# create the terminal scoped to our test environment
|
|
173
|
+
terminal_opts = {homedir: config.homedir}
|
|
174
|
+
Thread.current[:terminal] = Morpheus::Terminal.new(terminal_opts)
|
|
175
|
+
|
|
176
|
+
# debug is not a terminal option right now, set it this way instead...
|
|
177
|
+
if config.debug # || Morpheus::Logging.debug?
|
|
178
|
+
# Morpheus::Logging.set_log_level(Morpheus::Logging::Logger::DEBUG)
|
|
179
|
+
terminal.execute("debug")
|
|
180
|
+
else
|
|
181
|
+
if QUIET
|
|
182
|
+
#blackhole = Morpheus::Terminal::Blackhole.new('blackhole')
|
|
183
|
+
blackhole = File.open(Morpheus::Cli.windows? ? 'NUL:' : '/dev/null', 'w')
|
|
184
|
+
terminal.set_stdout(blackhole)
|
|
185
|
+
terminal.set_stderr(blackhole)
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
#terminal.execute("remote get \"#{config.remote_name}\" -q || remote add \"#{config.remote_name}\" \"#{config.url}\" --insecure --use -N")
|
|
190
|
+
appliance = Morpheus::Cli::Remote.load_remote(config.remote_name)
|
|
191
|
+
if appliance
|
|
192
|
+
# appliance already exists, update it if needed
|
|
193
|
+
if appliance[:url] != config.url
|
|
194
|
+
terminal.execute("remote update \"#{config.remote_name}\" --url \"#{escape_arg config.url}\" --insecure")
|
|
195
|
+
end
|
|
196
|
+
else
|
|
197
|
+
# create appliance
|
|
198
|
+
terminal.execute("remote add \"#{config.remote_name}\" \"#{escape_arg config.url}\" --insecure --use -N")
|
|
199
|
+
end
|
|
200
|
+
sleep 1
|
|
201
|
+
# use the remote
|
|
202
|
+
terminal.execute("remote use \"#{config.remote_name}\"")
|
|
203
|
+
|
|
204
|
+
# could skip this and just let the tests fail that require authentication...
|
|
205
|
+
# login right away to make sure credentials work before attempting to run test suite
|
|
206
|
+
# abort if bad credentials
|
|
207
|
+
#login_status_code, login_error = login()
|
|
208
|
+
login_results = terminal.execute(%(login "#{escape_arg config.username}" "#{escape_arg config.password_decrypted}"))
|
|
209
|
+
if login_results[0] == 0
|
|
210
|
+
#print_green_success "Established terminal to #{config.username}@#{config.url}\nStarting the test run now"
|
|
211
|
+
else
|
|
212
|
+
print_red_alert "Failed to login as #{config.username}@#{config.url}"
|
|
213
|
+
abort("Test run aborted")
|
|
214
|
+
exit 1
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
return Thread.current[:terminal]
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# use the test remote quietly
|
|
221
|
+
def use_remote()
|
|
222
|
+
config = get_config()
|
|
223
|
+
terminal.execute %(remote use #{config.remote_name} --quiet)
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
alias :remote_use :use_remote
|
|
227
|
+
|
|
228
|
+
# @return [true|false]
|
|
229
|
+
def is_logged_in()
|
|
230
|
+
return get_access_token() != nil
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# Fetch the access token for the current remote.
|
|
234
|
+
# @return [String,nil]
|
|
235
|
+
def get_access_token()
|
|
236
|
+
config = get_config()
|
|
237
|
+
appliance = Morpheus::Cli::Remote.load_remote(config.remote_name)
|
|
238
|
+
if appliance.nil?
|
|
239
|
+
#abort("test appliance not found #{config.remote_name}")
|
|
240
|
+
return nil
|
|
241
|
+
end
|
|
242
|
+
wallet = ::Morpheus::Cli::Credentials.new(appliance[:name], nil).load_saved_credentials()
|
|
243
|
+
if wallet.nil?
|
|
244
|
+
return nil
|
|
245
|
+
end
|
|
246
|
+
return wallet['access_token']
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
def login_if_needed()
|
|
250
|
+
if !is_logged_in()
|
|
251
|
+
login()
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
def logout_if_needed()
|
|
256
|
+
if is_logged_in()
|
|
257
|
+
logout()
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
# def login()
|
|
262
|
+
# config = get_config()
|
|
263
|
+
# result = terminal.execute(%(login "#{escape_arg config.username}" "#{escape_arg config.password_decrypted}" --quiet))
|
|
264
|
+
# exit_code, err = Morpheus::Cli::CliRegistry.parse_command_result(result)
|
|
265
|
+
# if exit_code != 0
|
|
266
|
+
# abort("Failed to login as #{config.username}@#{config.url}")
|
|
267
|
+
# else
|
|
268
|
+
# # hooray
|
|
269
|
+
# end
|
|
270
|
+
# end
|
|
271
|
+
|
|
272
|
+
def login()
|
|
273
|
+
config = get_config()
|
|
274
|
+
terminal.execute(%(login "#{escape_arg config.username}" "#{escape_arg config.password_decrypted}" --quiet))
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
def logout()
|
|
278
|
+
terminal.execute("logout --quiet")
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
# login to execute block and return to previous logged in/out state
|
|
282
|
+
def with_authentication(&block)
|
|
283
|
+
was_logged_out = !is_logged_in()
|
|
284
|
+
login_if_needed()
|
|
285
|
+
result = block.call()
|
|
286
|
+
logout_if_needed() if was_logged_out
|
|
287
|
+
return result
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
# logout to execute block and return to previous logged in/out state
|
|
291
|
+
def without_authentication(&block)
|
|
292
|
+
was_logged_in = is_logged_in()
|
|
293
|
+
logout_if_needed()
|
|
294
|
+
result = block.call()
|
|
295
|
+
login_if_needed() if was_logged_in
|
|
296
|
+
return result
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
# send input to CLI terminal stdin to handle interactive prompting
|
|
300
|
+
def with_input(*messages, &block)
|
|
301
|
+
with_tmpfile_input(*messages, &block)
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
# send input to CLI shell stdin to handle interactive prompting
|
|
305
|
+
# def with_shell_input(*messages, &block)
|
|
306
|
+
# with_tmpfile_input(*messages, &block)
|
|
307
|
+
# end
|
|
308
|
+
|
|
309
|
+
# Set stdin to a File (Readline requires a File object for input=)
|
|
310
|
+
# this way works for all testing purposes
|
|
311
|
+
def with_tmpfile_input(*messages, &block)
|
|
312
|
+
messages = messages.flatten #.compact
|
|
313
|
+
original_stdin = $stdin
|
|
314
|
+
#todo: use temp directory...
|
|
315
|
+
tmpfilename = ".cli_unit_test_shell_input_#{SecureRandom.hex(10)}.morpheus"
|
|
316
|
+
file = nil
|
|
317
|
+
begin
|
|
318
|
+
File.open(tmpfilename, 'w+') {|f| f.write(messages.join("\n") + "\n") }
|
|
319
|
+
file = File.new(tmpfilename, 'r')
|
|
320
|
+
Readline.input = file
|
|
321
|
+
$stdin = file
|
|
322
|
+
yield
|
|
323
|
+
ensure
|
|
324
|
+
Readline.input = original_stdin
|
|
325
|
+
$stdin = original_stdin
|
|
326
|
+
file.close if file && !file.closed?
|
|
327
|
+
File.delete(tmpfilename) if File.exist?(tmpfilename)
|
|
328
|
+
end
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
# escape double quotes for interpolating values between double quotes in your terminal command arguments
|
|
332
|
+
def escape_arg(value)
|
|
333
|
+
# escape double quotes
|
|
334
|
+
value.to_s.gsub("\"", "\\\"")
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
end
|
|
338
|
+
end
|
data/test/test_config.rb
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# use a different default home directory when running tests
|
|
2
|
+
ENV['MORPHEUS_CLI_HOME'] = ENV['TEST_CLI_HOME'] || File.join(Dir.home, ".morpheus_test") unless ENV['MORPHEUS_CLI_HOME']
|
|
3
|
+
require 'morpheus'
|
|
4
|
+
|
|
5
|
+
module MorpheusTest
|
|
6
|
+
|
|
7
|
+
# TestConfig handles parsing of the Morpheus test config settings
|
|
8
|
+
# This parses environment variables or a config file, the latter takes priority
|
|
9
|
+
# The default config file named +test_config.yaml+
|
|
10
|
+
class TestConfig
|
|
11
|
+
include Morpheus::Cli::PrintHelper # for parse_json_or_yaml() todo: remove this include and use a class method
|
|
12
|
+
|
|
13
|
+
# The default filename for the test config
|
|
14
|
+
DEFAULT_FILENAME = 'test_config.yaml'
|
|
15
|
+
|
|
16
|
+
# The environment variables that are parsed and the corresponding config property name
|
|
17
|
+
ENVIRONMENT_VARIABLES = {
|
|
18
|
+
# "TEST_CLI_HOME" => :homedir,
|
|
19
|
+
"TEST_REMOTE_NAME" => :remote_name,
|
|
20
|
+
"TEST_REMOTE_URL" => :url,
|
|
21
|
+
"TEST_URL" => :url,
|
|
22
|
+
"TEST_USERNAME" => :username,
|
|
23
|
+
"TEST_PASSWORD" => :password,
|
|
24
|
+
"TEST_DEBUG" => :debug,
|
|
25
|
+
"DEBUG" => :debug,
|
|
26
|
+
}.freeze
|
|
27
|
+
|
|
28
|
+
# The default name of the remote to create and do our unit testing in
|
|
29
|
+
DEFAULT_REMOTE_NAME = 'unit_test'
|
|
30
|
+
|
|
31
|
+
# use a different default home directory when running tests
|
|
32
|
+
# load settings from environment variables and config file
|
|
33
|
+
# unless ENV['MORPHEUS_CLI_HOME']
|
|
34
|
+
# ENV['MORPHEUS_CLI_HOME'] = File.join(Dir.home, ".morpheus_test")
|
|
35
|
+
# end
|
|
36
|
+
# The default name of the remote to create and use for unit testing
|
|
37
|
+
DEFAULT_HOMEDIR = Morpheus::Cli.home_directory
|
|
38
|
+
|
|
39
|
+
# raised if the config cannot be parsed or is missing required settings
|
|
40
|
+
class BadConfigError < StandardError
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
attr_accessor :homedir, :remote_name, :url, :username
|
|
44
|
+
attr_writer :password
|
|
45
|
+
attr_reader :debug
|
|
46
|
+
|
|
47
|
+
def initialize
|
|
48
|
+
# set defaults
|
|
49
|
+
@remote_name = DEFAULT_REMOTE_NAME # 'unit_test'
|
|
50
|
+
@homedir = DEFAULT_HOMEDIR # Morpheus::Cli.home_directory
|
|
51
|
+
|
|
52
|
+
# load settings from environment variable and set instance variables
|
|
53
|
+
#ENVIRONMENT_VARIABLES.each { |name, value| instance_variable_set("@#{name}", ENV[name]) if ENV[name] }
|
|
54
|
+
ENVIRONMENT_VARIABLES.each { |name, value| send("#{value}=", ENV[name]) if ENV[name] }
|
|
55
|
+
|
|
56
|
+
# load settings from config file
|
|
57
|
+
config_filename = nil
|
|
58
|
+
if ENV['TEST_CONFIG']
|
|
59
|
+
config_filename = ENV['TEST_CONFIG']
|
|
60
|
+
elsif File.exist?(DEFAULT_FILENAME)
|
|
61
|
+
config_filename = DEFAULT_FILENAME
|
|
62
|
+
elsif File.exist?(File.join(homedir, DEFAULT_FILENAME))
|
|
63
|
+
config_filename = File.join(@homedir, DEFAULT_FILENAME)
|
|
64
|
+
end
|
|
65
|
+
if config_filename
|
|
66
|
+
full_filename = File.expand_path(config_filename)
|
|
67
|
+
file_content = nil
|
|
68
|
+
if File.exist?(full_filename)
|
|
69
|
+
file_content = File.read(full_filename)
|
|
70
|
+
else
|
|
71
|
+
raise "Test config file not found: #{full_filename}"
|
|
72
|
+
end
|
|
73
|
+
parse_result = parse_json_or_yaml(file_content)
|
|
74
|
+
config_map = parse_result[:data]
|
|
75
|
+
if config_map.nil?
|
|
76
|
+
bad_config "Failed to parse test config file '#{config_filename}' as YAML or JSON. Error: #{parse_result[:error]}"
|
|
77
|
+
end
|
|
78
|
+
@homedir = config_map['homedir'] if config_map['homedir']
|
|
79
|
+
@remote_name = config_map['remote_name'] if config_map['remote_name']
|
|
80
|
+
@url = (config_map['url'] || config_map['remote_url']) if (config_map['url'] || config_map['remote_url'])
|
|
81
|
+
@username = config_map['username'] if config_map['username']
|
|
82
|
+
@password = config_map['password'] if config_map['password']
|
|
83
|
+
@debug = config_map['debug'].to_s.strip.downcase == "true" if config_map.key?('debug')
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# print_h1 "Test Config"
|
|
87
|
+
# print cyan
|
|
88
|
+
# puts "homedir: #{homedir}"
|
|
89
|
+
# puts "remote_name: #{remote_name}"
|
|
90
|
+
# puts "url: #{url}"
|
|
91
|
+
# puts "username: #{username}"
|
|
92
|
+
# puts "password: #{password}"
|
|
93
|
+
# print reset
|
|
94
|
+
|
|
95
|
+
# validate now
|
|
96
|
+
# For now force user to create a test_config.yaml or use environment variables
|
|
97
|
+
if remote_name.to_s.empty?
|
|
98
|
+
bad_config "Unable to execute unit tests without specifying a remote name.\nYou must specify the environment variable TEST_REMOTE_NAME=unit_test\nor in a config file named #{config_filename || 'test_config.yaml'} like this:\n\nremote_name: unit_test\n"
|
|
99
|
+
end
|
|
100
|
+
if url.to_s.empty?
|
|
101
|
+
bad_config "Unable to execute unit tests without specifying a url.\nThis can be specified with the environment variable TEST_URL=https://test-appliance\nor in a config file named #{config_filename || 'test_config.yaml'} like this:\n\nurl: https://test-appliance\n"
|
|
102
|
+
end
|
|
103
|
+
if username.to_s.empty?
|
|
104
|
+
bad_config "Unable to execute unit tests without specifying a username.\nThis can be specified with the environment variable TEST_USERNAME=testrunner\nor in a config file named #{config_filename || 'test_config.yaml'} like this:\n\nusername: testrunner\n"
|
|
105
|
+
end
|
|
106
|
+
if password.to_s.empty?
|
|
107
|
+
bad_config "Unable to execute unit tests without specifying a password.\nThis can be specified with the environment variable TEST_PASSWORD='SecretPassword123$'\nor in a config file named #{config_filename || 'test_config.yaml'} like this:\n\npassword: 'SecretPassword123$'\n"
|
|
108
|
+
end
|
|
109
|
+
# debug is not a terminal method right now, set it this way to enable morpheus debugging right away
|
|
110
|
+
if debug
|
|
111
|
+
Morpheus::Logging.set_log_level(Morpheus::Logging::Logger::DEBUG)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# todo: test authentication now...
|
|
115
|
+
# config looks good
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def password
|
|
119
|
+
@password ? ('*' * 12) : nil
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def password_decrypted
|
|
123
|
+
@password
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def debug=(v)
|
|
127
|
+
@debug = v.to_s.strip.downcase == "true"
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def bad_config(msg)
|
|
131
|
+
#todo: make this work with raise but also don't run every test so just abort for now
|
|
132
|
+
#raise BadConfigError.new(msg)
|
|
133
|
+
print_red_alert msg
|
|
134
|
+
abort("Aborting tests...")
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
module MorpheusTest
|
|
2
|
+
|
|
3
|
+
# Mixin to provide loading test data / fixtures for tests
|
|
4
|
+
module TestDataHelper
|
|
5
|
+
|
|
6
|
+
def self.included(base)
|
|
7
|
+
#base.extend ClassMethods
|
|
8
|
+
#todo: load test/fixtures/*yaml
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
protected
|
|
12
|
+
|
|
13
|
+
## Instances
|
|
14
|
+
|
|
15
|
+
def find_first_instance_id()
|
|
16
|
+
instance_id = client.instances.list({})['instances'].collect {|it| it['id'] }.first
|
|
17
|
+
assert_not_nil instance_id, "Failed to find an instance to test with"
|
|
18
|
+
return instance_id
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def find_many_instance_ids()
|
|
22
|
+
instance_ids = client.instances.list({})['instances'].collect {|it| it['id'] }.first(5)
|
|
23
|
+
assert instance_ids.size > 1, "Failed to find many instances to test with"
|
|
24
|
+
return instance_ids
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# populates test with the following data as instance variables
|
|
28
|
+
# @container - Container
|
|
29
|
+
# @id - Container id
|
|
30
|
+
#todo: create a container instead...
|
|
31
|
+
# maybe just warn and omit the test, but it asserts and fails now...
|
|
32
|
+
# instead of env, have a test/fixtures/test-instance-payload.json or
|
|
33
|
+
# or maybe put it in the test_config.yaml under fixtures.
|
|
34
|
+
def load_instance_test_data()
|
|
35
|
+
# todo: cache this , maybe Thread.current for now...
|
|
36
|
+
@id = nil
|
|
37
|
+
|
|
38
|
+
test_instance_id = ENV['TEST_INSTANCE_ID']
|
|
39
|
+
if test_instance_id
|
|
40
|
+
# use the first container in our test instance
|
|
41
|
+
@instance = client.instances.get(test_instance_id.to_i)['instance']
|
|
42
|
+
# rescue ::RestClient::Exception => e on 404
|
|
43
|
+
assert_not_nil @instance, "Test instance #{test_instance_id} was not found!"
|
|
44
|
+
@container = client.containers.get(@instance['containers'].first)['container']
|
|
45
|
+
@id = @instance['id']
|
|
46
|
+
end
|
|
47
|
+
assert_not_nil @id, "A test instance must be specified to run this test.\nTry setting environment variable TEST_CONTAINER_ID=42 or TEST_INSTANCE_ID=99"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
## Containers
|
|
51
|
+
|
|
52
|
+
def find_first_container_id()
|
|
53
|
+
container_id = client.instances.list({})['instances'].collect {|it| it['containers'] }.flatten.uniq.first
|
|
54
|
+
assert_not_nil container_id, "Failed to find a container to test with"
|
|
55
|
+
return container_id
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def find_many_container_ids()
|
|
59
|
+
container_ids = client.instances.list({})['instances'].collect {|it| it['containers'] }.flatten.uniq.first(5)
|
|
60
|
+
assert container_ids.size > 1, "Failed to find many containers to test with"
|
|
61
|
+
return container_ids
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# populates test with the following data as instance variables
|
|
65
|
+
# @container - Container
|
|
66
|
+
# @id - Container id
|
|
67
|
+
#todo: create a container instead...
|
|
68
|
+
# maybe just warn and omit the test, but it asserts and fails now...
|
|
69
|
+
# instead of env, have a test/fixtures/test-instance-payload.json or
|
|
70
|
+
# or maybe put it in the test_config.yaml under fixtures.
|
|
71
|
+
def load_container_test_data()
|
|
72
|
+
# todo: cache this , maybe Thread.current for now...
|
|
73
|
+
@id = nil
|
|
74
|
+
test_container_id = ENV['TEST_CONTAINER_ID']
|
|
75
|
+
test_instance_id = ENV['TEST_INSTANCE_ID']
|
|
76
|
+
if test_container_id
|
|
77
|
+
# use our test container
|
|
78
|
+
@container = client.containers.get(test_container_id)['container']
|
|
79
|
+
# rescue ::RestClient::Exception => e on 404
|
|
80
|
+
assert_not_nil @container, "Container #{test_container_id} was not found"
|
|
81
|
+
@id = @container['id']
|
|
82
|
+
elsif test_instance_id
|
|
83
|
+
# use the first container in our test instance
|
|
84
|
+
@instance = client.instances.get(test_instance_id.to_i)['instance']
|
|
85
|
+
# rescue ::RestClient::Exception => e on 404
|
|
86
|
+
assert_not_nil @instance, "Test instance #{test_instance_id} was not found!"
|
|
87
|
+
assert_not_nil @instance['containers'].first, "Instance #{@instance['id']} does not have any containers\nTry setting environment variable TEST_CONTAINER_ID=42"
|
|
88
|
+
@container = client.containers.get(@instance['containers'].first)['container']
|
|
89
|
+
assert_not_nil @container, "Container #{@instance['containers'].first} was not found"
|
|
90
|
+
@id = @container['id']
|
|
91
|
+
end
|
|
92
|
+
assert_not_nil @id, "A test container must be specified to run this test.\nTry setting environment variable TEST_CONTAINER_ID=42 or TEST_INSTANCE_ID=99"
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: morpheus-cli
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 5.5.
|
|
4
|
+
version: 5.5.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- David Estes
|
|
@@ -11,7 +11,7 @@ authors:
|
|
|
11
11
|
autorequire:
|
|
12
12
|
bindir: bin
|
|
13
13
|
cert_chain: []
|
|
14
|
-
date: 2022-
|
|
14
|
+
date: 2022-12-14 00:00:00.000000000 Z
|
|
15
15
|
dependencies:
|
|
16
16
|
- !ruby/object:Gem::Dependency
|
|
17
17
|
name: bundler
|
|
@@ -153,6 +153,20 @@ dependencies:
|
|
|
153
153
|
- - ">="
|
|
154
154
|
- !ruby/object:Gem::Version
|
|
155
155
|
version: '0'
|
|
156
|
+
- !ruby/object:Gem::Dependency
|
|
157
|
+
name: test-unit
|
|
158
|
+
requirement: !ruby/object:Gem::Requirement
|
|
159
|
+
requirements:
|
|
160
|
+
- - ">="
|
|
161
|
+
- !ruby/object:Gem::Version
|
|
162
|
+
version: '0'
|
|
163
|
+
type: :runtime
|
|
164
|
+
prerelease: false
|
|
165
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
166
|
+
requirements:
|
|
167
|
+
- - ">="
|
|
168
|
+
- !ruby/object:Gem::Version
|
|
169
|
+
version: '0'
|
|
156
170
|
description: Infrastructure agnostic cloud application management & orchestration
|
|
157
171
|
CLI for Morpheus. Easily manage and orchestrate VMS on private or public infrastructure
|
|
158
172
|
and containerized architectures.
|
|
@@ -550,6 +564,28 @@ files:
|
|
|
550
564
|
- lib/morpheus/terminal.rb
|
|
551
565
|
- lib/morpheus/util.rb
|
|
552
566
|
- morpheus-cli.gemspec
|
|
567
|
+
- test/api/containers_interface_test.rb
|
|
568
|
+
- test/api/doc_interface_test.rb
|
|
569
|
+
- test/api/instances_interface_test.rb
|
|
570
|
+
- test/api/whoami_interface_test.rb
|
|
571
|
+
- test/cli/access_token_test.rb
|
|
572
|
+
- test/cli/auth_test.rb
|
|
573
|
+
- test/cli/cli_test.rb
|
|
574
|
+
- test/cli/containers_test.rb
|
|
575
|
+
- test/cli/doc_test.rb
|
|
576
|
+
- test/cli/help_test.rb
|
|
577
|
+
- test/cli/instances_test.rb
|
|
578
|
+
- test/cli/man_test.rb
|
|
579
|
+
- test/cli/remote_test.rb
|
|
580
|
+
- test/cli/roles_test.rb
|
|
581
|
+
- test/cli/shell_test.rb
|
|
582
|
+
- test/cli/version_test.rb
|
|
583
|
+
- test/cli/view_test.rb
|
|
584
|
+
- test/cli/whoami_test.rb
|
|
585
|
+
- test/morpheus_test.rb
|
|
586
|
+
- test/test_case.rb
|
|
587
|
+
- test/test_config.rb
|
|
588
|
+
- test/test_data_helper.rb
|
|
553
589
|
homepage:
|
|
554
590
|
licenses:
|
|
555
591
|
- MIT
|
|
@@ -573,4 +609,26 @@ rubygems_version: 3.1.6
|
|
|
573
609
|
signing_key:
|
|
574
610
|
specification_version: 4
|
|
575
611
|
summary: Provides CLI Interface to the Morpheus Public/Private Cloud Appliance
|
|
576
|
-
test_files:
|
|
612
|
+
test_files:
|
|
613
|
+
- test/api/containers_interface_test.rb
|
|
614
|
+
- test/api/doc_interface_test.rb
|
|
615
|
+
- test/api/instances_interface_test.rb
|
|
616
|
+
- test/api/whoami_interface_test.rb
|
|
617
|
+
- test/cli/access_token_test.rb
|
|
618
|
+
- test/cli/auth_test.rb
|
|
619
|
+
- test/cli/cli_test.rb
|
|
620
|
+
- test/cli/containers_test.rb
|
|
621
|
+
- test/cli/doc_test.rb
|
|
622
|
+
- test/cli/help_test.rb
|
|
623
|
+
- test/cli/instances_test.rb
|
|
624
|
+
- test/cli/man_test.rb
|
|
625
|
+
- test/cli/remote_test.rb
|
|
626
|
+
- test/cli/roles_test.rb
|
|
627
|
+
- test/cli/shell_test.rb
|
|
628
|
+
- test/cli/version_test.rb
|
|
629
|
+
- test/cli/view_test.rb
|
|
630
|
+
- test/cli/whoami_test.rb
|
|
631
|
+
- test/morpheus_test.rb
|
|
632
|
+
- test/test_case.rb
|
|
633
|
+
- test/test_config.rb
|
|
634
|
+
- test/test_data_helper.rb
|