morpheus-cli 5.5.2.2 → 5.5.3.1
Sign up to get free protection for your applications and to get access to all the features.
- 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 +20 -2
- data/lib/morpheus/api/appliance_settings_interface.rb +15 -0
- 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/cypher_interface.rb +1 -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/guidance_settings_interface.rb +17 -0
- 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/monitoring_settings_interface.rb +25 -0
- data/lib/morpheus/api/network_server_groups_interface.rb +7 -0
- 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 +79 -37
- 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/appliance_settings_command.rb +57 -2
- data/lib/morpheus/cli/commands/apps.rb +1 -1
- data/lib/morpheus/cli/commands/archives_command.rb +25 -33
- data/lib/morpheus/cli/commands/backup_settings_command.rb +1 -1
- 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 +18 -13
- 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/cypher_command.rb +3 -0
- 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/guidance_command.rb +2 -2
- data/lib/morpheus/cli/commands/guidance_settings.rb +148 -0
- 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/log_settings_command.rb +1 -1
- data/lib/morpheus/cli/commands/login.rb +1 -1
- data/lib/morpheus/cli/commands/man_command.rb +32 -18
- data/lib/morpheus/cli/commands/monitoring_settings.rb +228 -0
- data/lib/morpheus/cli/commands/network_server_groups_command.rb +222 -0
- 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/provisioning_settings_command.rb +1 -1
- data/lib/morpheus/cli/commands/remote.rb +1 -1
- data/lib/morpheus/cli/commands/reports_command.rb +13 -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 +5 -5
- 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/provisioning_helper.rb +14 -12
- data/lib/morpheus/cli/mixins/rest_command.rb +23 -19
- data/lib/morpheus/cli/mixins/secondary_rest_command.rb +47 -24
- 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/ext/string.rb +6 -4
- 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 +41 -9
- 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 +67 -3
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'morpheus_test'
|
2
|
+
|
3
|
+
class MorpheusTest::ShellTest < MorpheusTest::TestCase
|
4
|
+
|
5
|
+
def requires_remote
|
6
|
+
false
|
7
|
+
end
|
8
|
+
|
9
|
+
def requires_authentication
|
10
|
+
false
|
11
|
+
end
|
12
|
+
|
13
|
+
# class << self
|
14
|
+
# def startup
|
15
|
+
# # one time use remote and login at beginning of testsuite, not each method
|
16
|
+
# use_remote()
|
17
|
+
# login()
|
18
|
+
# end
|
19
|
+
# end
|
20
|
+
|
21
|
+
# def test_remote_use
|
22
|
+
# # use remote needed for all except test_shell_clean() , todo: exclude paradigm that yet..
|
23
|
+
# use_remote()
|
24
|
+
# end
|
25
|
+
|
26
|
+
def test_shell
|
27
|
+
with_input "exit" do
|
28
|
+
assert_execute "shell"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_shell_verbose
|
33
|
+
login_if_needed()
|
34
|
+
with_input "whoami", "exit" do
|
35
|
+
assert_execute "shell -V"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_shell_history
|
40
|
+
with_input "history", "exit" do
|
41
|
+
assert_execute "shell"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_shell_sleep
|
46
|
+
with_input "echo \"It is time for a rest.\"", "sleep 0.5", "echo \"OK, let's keep testing\"", "exit" do
|
47
|
+
assert_execute "shell -V"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_shell_temporary
|
52
|
+
with_input "remote list", "remote current", "echo this is a temporary shell with no history", "history", "exit" do
|
53
|
+
assert_execute "shell -Z -V"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_shell_confirmation
|
58
|
+
with_input "access-token refresh", "I'm not sure...", "no", "exit" do
|
59
|
+
assert_execute "shell -Z -V"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# todo: fix bug where appliances are not restored after clean shell.. so remote use #{@config.remote_name}" starts failing here..
|
64
|
+
=begin
|
65
|
+
def test_shell_clean
|
66
|
+
with_input "echo this is a clean shell with no remotes or history", "remote list", "remote current", "history", "exit" do
|
67
|
+
assert_execute "shell -z"
|
68
|
+
end
|
69
|
+
with_input "echo this is another clean shell", "exit" do
|
70
|
+
assert_execute "shell --clean"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def test_shell_history_again
|
75
|
+
with_input "echo back in test shell again with our remote and history", "remote get #{@config.remote_name}", "history", "exit" do
|
76
|
+
assert_execute "shell"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
=end
|
80
|
+
|
81
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'morpheus_test'
|
2
|
+
|
3
|
+
# Tests for Morpheus::Cli::VersionCommand
|
4
|
+
class MorpheusTest::VersionTest < MorpheusTest::TestCase
|
5
|
+
|
6
|
+
def requires_authentication
|
7
|
+
false
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_version
|
11
|
+
assert_execute %(version)
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_version_short
|
15
|
+
assert_execute %(version -v)
|
16
|
+
assert_execute %(version --short)
|
17
|
+
end
|
18
|
+
|
19
|
+
# def test_version_help
|
20
|
+
# assert_execute %(version --help)
|
21
|
+
# end
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'morpheus_test'
|
2
|
+
|
3
|
+
# Tests for Morpheus::Cli::View class, corresponds to the `view` CLI command
|
4
|
+
class MorpheusTest::ViewTest < MorpheusTest::TestCase
|
5
|
+
|
6
|
+
def teardown
|
7
|
+
# todo: close browser after each test
|
8
|
+
end
|
9
|
+
|
10
|
+
# these all pass but it's kind of obnoxious to open so many tabs
|
11
|
+
|
12
|
+
=begin
|
13
|
+
def test_view
|
14
|
+
assert_execute %(view)
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_view_login
|
18
|
+
assert_execute %(view --login)
|
19
|
+
#puts "pausing a moment while logging in with browser"
|
20
|
+
#sleep(3)
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_view_login_short
|
25
|
+
assert_execute %(view -l)
|
26
|
+
#puts "pausing a moment while logging in with browser"
|
27
|
+
#sleep(3)
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_view_clouds
|
31
|
+
assert_execute %(view clouds)
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_view_cloud_by_id
|
35
|
+
cloud = client.clouds.list({})['zones'][0]
|
36
|
+
if cloud
|
37
|
+
assert_execute %(view cloud "#{escape_arg cloud['id']}")
|
38
|
+
else
|
39
|
+
puts "No cloud found, unable to execute test `#{__method__}`"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_view_instance_by_name
|
44
|
+
assert_execute %(view -l)
|
45
|
+
instance = client.instances.list({})['instances'][0]
|
46
|
+
if instance
|
47
|
+
assert_execute %(view instance "#{escape_arg instance['name']}")
|
48
|
+
else
|
49
|
+
puts "No instance found, unable to execute test `#{__method__}`"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
=end
|
53
|
+
protected
|
54
|
+
|
55
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'morpheus_test'
|
2
|
+
|
3
|
+
class MorpheusTest::WhoamiTest < MorpheusTest::TestCase
|
4
|
+
|
5
|
+
def test_whoami
|
6
|
+
assert_execute("whoami")
|
7
|
+
assert_execute("whoami --name")
|
8
|
+
assert_execute("whoami --permissions")
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_whoami_unauthorized
|
12
|
+
without_authentication do
|
13
|
+
assert_error("whoami", "Expected error while unauthorized")
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'test_case'
|
3
|
+
require 'test_data_helper'
|
4
|
+
|
5
|
+
# setup at_start hook that runs once at the beginning of all tests
|
6
|
+
Test::Unit.at_start {
|
7
|
+
|
8
|
+
}
|
9
|
+
|
10
|
+
# setup at_exit hook that runs once at the end of all tests
|
11
|
+
Test::Unit.at_exit {
|
12
|
+
# always logout when all done
|
13
|
+
# logout_if_needed()
|
14
|
+
# terminal.execute("logout") if is_logged_in()
|
15
|
+
Morpheus::Terminal.instance.execute("logout")
|
16
|
+
}
|
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
|