morpheus-cli 5.5.2.2 → 5.5.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -1
  3. data/Dockerfile +1 -1
  4. data/README.md +57 -4
  5. data/Rakefile +9 -0
  6. data/bin/morpheus +4 -4
  7. data/lib/morpheus/api/api_client.rb +20 -2
  8. data/lib/morpheus/api/appliance_settings_interface.rb +15 -0
  9. data/lib/morpheus/api/archive_buckets_interface.rb +1 -1
  10. data/lib/morpheus/api/archive_files_interface.rb +3 -3
  11. data/lib/morpheus/api/clients_interface.rb +2 -2
  12. data/lib/morpheus/api/clusters_interface.rb +8 -1
  13. data/lib/morpheus/api/containers_interface.rb +29 -16
  14. data/lib/morpheus/api/custom_instance_types_interface.rb +0 -2
  15. data/lib/morpheus/api/cypher_interface.rb +1 -2
  16. data/lib/morpheus/api/doc_interface.rb +8 -6
  17. data/lib/morpheus/api/file_copy_request_interface.rb +1 -1
  18. data/lib/morpheus/api/guidance_settings_interface.rb +17 -0
  19. data/lib/morpheus/api/health_interface.rb +1 -1
  20. data/lib/morpheus/api/image_builder_interface.rb +3 -3
  21. data/lib/morpheus/api/instances_interface.rb +25 -0
  22. data/lib/morpheus/api/logs_interface.rb +2 -4
  23. data/lib/morpheus/api/monitoring_interface.rb +6 -6
  24. data/lib/morpheus/api/monitoring_settings_interface.rb +25 -0
  25. data/lib/morpheus/api/network_server_groups_interface.rb +7 -0
  26. data/lib/morpheus/api/packages_interface.rb +1 -1
  27. data/lib/morpheus/api/reports_interface.rb +1 -1
  28. data/lib/morpheus/api/servers_interface.rb +9 -1
  29. data/lib/morpheus/api/storage_providers_interface.rb +2 -2
  30. data/lib/morpheus/api/virtual_images_interface.rb +1 -1
  31. data/lib/morpheus/api.rb +2 -0
  32. data/lib/morpheus/benchmarking.rb +1 -1
  33. data/lib/morpheus/cli/cli_command.rb +79 -37
  34. data/lib/morpheus/cli/cli_registry.rb +19 -10
  35. data/lib/morpheus/cli/commands/access_token_command.rb +1 -1
  36. data/lib/morpheus/cli/commands/appliance_settings_command.rb +57 -2
  37. data/lib/morpheus/cli/commands/apps.rb +1 -1
  38. data/lib/morpheus/cli/commands/archives_command.rb +25 -33
  39. data/lib/morpheus/cli/commands/backup_settings_command.rb +1 -1
  40. data/lib/morpheus/cli/commands/blueprints_command.rb +10 -21
  41. data/lib/morpheus/cli/commands/boot_scripts_command.rb +2 -2
  42. data/lib/morpheus/cli/commands/cat_command.rb +1 -1
  43. data/lib/morpheus/cli/commands/catalog_item_types_command.rb +18 -13
  44. data/lib/morpheus/cli/commands/clouds.rb +3 -3
  45. data/lib/morpheus/cli/commands/clusters.rb +154 -3
  46. data/lib/morpheus/cli/commands/containers_command.rb +398 -253
  47. data/lib/morpheus/cli/commands/cypher_command.rb +3 -0
  48. data/lib/morpheus/cli/commands/deployments.rb +1 -1
  49. data/lib/morpheus/cli/commands/deploys.rb +9 -9
  50. data/lib/morpheus/cli/commands/doc.rb +15 -16
  51. data/lib/morpheus/cli/commands/execution_request_command.rb +2 -2
  52. data/lib/morpheus/cli/commands/file_copy_request_command.rb +5 -5
  53. data/lib/morpheus/cli/commands/groups.rb +2 -2
  54. data/lib/morpheus/cli/commands/guidance_command.rb +2 -2
  55. data/lib/morpheus/cli/commands/guidance_settings.rb +148 -0
  56. data/lib/morpheus/cli/commands/health_command.rb +4 -4
  57. data/lib/morpheus/cli/commands/hosts.rb +43 -5
  58. data/lib/morpheus/cli/commands/image_builder_command.rb +1 -1
  59. data/lib/morpheus/cli/commands/instances.rb +419 -148
  60. data/lib/morpheus/cli/commands/integrations_command.rb +22 -20
  61. data/lib/morpheus/cli/commands/key_pairs.rb +2 -2
  62. data/lib/morpheus/cli/commands/library_container_scripts_command.rb +2 -2
  63. data/lib/morpheus/cli/commands/library_container_templates_command.rb +2 -2
  64. data/lib/morpheus/cli/commands/library_instance_types_command.rb +3 -3
  65. data/lib/morpheus/cli/commands/library_spec_templates_command.rb +2 -2
  66. data/lib/morpheus/cli/commands/log_settings_command.rb +1 -1
  67. data/lib/morpheus/cli/commands/login.rb +1 -1
  68. data/lib/morpheus/cli/commands/man_command.rb +32 -18
  69. data/lib/morpheus/cli/commands/monitoring_settings.rb +228 -0
  70. data/lib/morpheus/cli/commands/network_server_groups_command.rb +222 -0
  71. data/lib/morpheus/cli/commands/packages_command.rb +11 -11
  72. data/lib/morpheus/cli/commands/plugins.rb +1 -1
  73. data/lib/morpheus/cli/commands/policies_command.rb +4 -4
  74. data/lib/morpheus/cli/commands/preseed_scripts_command.rb +2 -2
  75. data/lib/morpheus/cli/commands/provisioning_settings_command.rb +1 -1
  76. data/lib/morpheus/cli/commands/remote.rb +1 -1
  77. data/lib/morpheus/cli/commands/reports_command.rb +13 -3
  78. data/lib/morpheus/cli/commands/security_groups.rb +1 -1
  79. data/lib/morpheus/cli/commands/shell.rb +40 -62
  80. data/lib/morpheus/cli/commands/snapshots.rb +3 -5
  81. data/lib/morpheus/cli/commands/source_command.rb +8 -16
  82. data/lib/morpheus/cli/commands/storage_providers_command.rb +7 -7
  83. data/lib/morpheus/cli/commands/tasks.rb +2 -2
  84. data/lib/morpheus/cli/commands/vdi_pools_command.rb +6 -6
  85. data/lib/morpheus/cli/commands/view.rb +5 -1
  86. data/lib/morpheus/cli/commands/whitelabel_settings_command.rb +5 -5
  87. data/lib/morpheus/cli/commands/whoami.rb +2 -2
  88. data/lib/morpheus/cli/credentials.rb +30 -8
  89. data/lib/morpheus/cli/dot_file.rb +8 -15
  90. data/lib/morpheus/cli/error_handler.rb +16 -0
  91. data/lib/morpheus/cli/errors.rb +8 -1
  92. data/lib/morpheus/cli/mixins/print_helper.rb +17 -13
  93. data/lib/morpheus/cli/mixins/provisioning_helper.rb +14 -12
  94. data/lib/morpheus/cli/mixins/rest_command.rb +23 -19
  95. data/lib/morpheus/cli/mixins/secondary_rest_command.rb +47 -24
  96. data/lib/morpheus/cli/option_parser.rb +5 -1
  97. data/lib/morpheus/cli/option_types.rb +59 -12
  98. data/lib/morpheus/cli/version.rb +1 -1
  99. data/lib/morpheus/cli.rb +26 -16
  100. data/lib/morpheus/ext/rest_client.rb +3 -2
  101. data/lib/morpheus/ext/string.rb +6 -4
  102. data/lib/morpheus/formatters.rb +1 -1
  103. data/lib/morpheus/logging.rb +4 -4
  104. data/lib/morpheus/morpkg.rb +4 -4
  105. data/lib/morpheus/rest_client.rb +2 -2
  106. data/lib/morpheus/routes.rb +41 -9
  107. data/lib/morpheus/terminal.rb +65 -16
  108. data/lib/morpheus.rb +1 -1
  109. data/morpheus-cli.gemspec +1 -0
  110. data/test/api/containers_interface_test.rb +68 -0
  111. data/test/api/doc_interface_test.rb +35 -0
  112. data/test/api/instances_interface_test.rb +22 -0
  113. data/test/api/whoami_interface_test.rb +14 -0
  114. data/test/cli/access_token_test.rb +36 -0
  115. data/test/cli/auth_test.rb +82 -0
  116. data/test/cli/cli_test.rb +48 -0
  117. data/test/cli/containers_test.rb +92 -0
  118. data/test/cli/doc_test.rb +35 -0
  119. data/test/cli/help_test.rb +25 -0
  120. data/test/cli/instances_test.rb +36 -0
  121. data/test/cli/man_test.rb +14 -0
  122. data/test/cli/remote_test.rb +89 -0
  123. data/test/cli/roles_test.rb +34 -0
  124. data/test/cli/shell_test.rb +81 -0
  125. data/test/cli/version_test.rb +23 -0
  126. data/test/cli/view_test.rb +55 -0
  127. data/test/cli/whoami_test.rb +17 -0
  128. data/test/morpheus_test.rb +16 -0
  129. data/test/test_case.rb +338 -0
  130. data/test/test_config.rb +137 -0
  131. data/test/test_data_helper.rb +97 -0
  132. 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
@@ -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