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.
Files changed (117) 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 +8 -2
  8. data/lib/morpheus/api/archive_buckets_interface.rb +1 -1
  9. data/lib/morpheus/api/archive_files_interface.rb +3 -3
  10. data/lib/morpheus/api/clients_interface.rb +2 -2
  11. data/lib/morpheus/api/clusters_interface.rb +8 -1
  12. data/lib/morpheus/api/containers_interface.rb +29 -16
  13. data/lib/morpheus/api/custom_instance_types_interface.rb +0 -2
  14. data/lib/morpheus/api/doc_interface.rb +8 -6
  15. data/lib/morpheus/api/file_copy_request_interface.rb +1 -1
  16. data/lib/morpheus/api/health_interface.rb +1 -1
  17. data/lib/morpheus/api/image_builder_interface.rb +3 -3
  18. data/lib/morpheus/api/instances_interface.rb +25 -0
  19. data/lib/morpheus/api/logs_interface.rb +2 -4
  20. data/lib/morpheus/api/monitoring_interface.rb +6 -6
  21. data/lib/morpheus/api/packages_interface.rb +1 -1
  22. data/lib/morpheus/api/reports_interface.rb +1 -1
  23. data/lib/morpheus/api/servers_interface.rb +9 -1
  24. data/lib/morpheus/api/storage_providers_interface.rb +2 -2
  25. data/lib/morpheus/api/virtual_images_interface.rb +1 -1
  26. data/lib/morpheus/api.rb +2 -0
  27. data/lib/morpheus/benchmarking.rb +1 -1
  28. data/lib/morpheus/cli/cli_command.rb +69 -36
  29. data/lib/morpheus/cli/cli_registry.rb +19 -10
  30. data/lib/morpheus/cli/commands/access_token_command.rb +1 -1
  31. data/lib/morpheus/cli/commands/apps.rb +1 -1
  32. data/lib/morpheus/cli/commands/archives_command.rb +25 -33
  33. data/lib/morpheus/cli/commands/blueprints_command.rb +10 -21
  34. data/lib/morpheus/cli/commands/boot_scripts_command.rb +2 -2
  35. data/lib/morpheus/cli/commands/cat_command.rb +1 -1
  36. data/lib/morpheus/cli/commands/catalog_item_types_command.rb +12 -12
  37. data/lib/morpheus/cli/commands/clouds.rb +3 -3
  38. data/lib/morpheus/cli/commands/clusters.rb +154 -3
  39. data/lib/morpheus/cli/commands/containers_command.rb +398 -253
  40. data/lib/morpheus/cli/commands/deployments.rb +1 -1
  41. data/lib/morpheus/cli/commands/deploys.rb +9 -9
  42. data/lib/morpheus/cli/commands/doc.rb +15 -16
  43. data/lib/morpheus/cli/commands/execution_request_command.rb +2 -2
  44. data/lib/morpheus/cli/commands/file_copy_request_command.rb +5 -5
  45. data/lib/morpheus/cli/commands/groups.rb +2 -2
  46. data/lib/morpheus/cli/commands/health_command.rb +4 -4
  47. data/lib/morpheus/cli/commands/hosts.rb +43 -5
  48. data/lib/morpheus/cli/commands/image_builder_command.rb +1 -1
  49. data/lib/morpheus/cli/commands/instances.rb +419 -148
  50. data/lib/morpheus/cli/commands/integrations_command.rb +22 -20
  51. data/lib/morpheus/cli/commands/key_pairs.rb +2 -2
  52. data/lib/morpheus/cli/commands/library_container_scripts_command.rb +2 -2
  53. data/lib/morpheus/cli/commands/library_container_templates_command.rb +2 -2
  54. data/lib/morpheus/cli/commands/library_instance_types_command.rb +3 -3
  55. data/lib/morpheus/cli/commands/library_spec_templates_command.rb +2 -2
  56. data/lib/morpheus/cli/commands/login.rb +1 -1
  57. data/lib/morpheus/cli/commands/man_command.rb +32 -18
  58. data/lib/morpheus/cli/commands/packages_command.rb +11 -11
  59. data/lib/morpheus/cli/commands/plugins.rb +1 -1
  60. data/lib/morpheus/cli/commands/policies_command.rb +4 -4
  61. data/lib/morpheus/cli/commands/preseed_scripts_command.rb +2 -2
  62. data/lib/morpheus/cli/commands/remote.rb +1 -1
  63. data/lib/morpheus/cli/commands/reports_command.rb +3 -3
  64. data/lib/morpheus/cli/commands/roles.rb +6 -3
  65. data/lib/morpheus/cli/commands/security_groups.rb +1 -1
  66. data/lib/morpheus/cli/commands/shell.rb +40 -62
  67. data/lib/morpheus/cli/commands/snapshots.rb +3 -5
  68. data/lib/morpheus/cli/commands/source_command.rb +8 -16
  69. data/lib/morpheus/cli/commands/storage_providers_command.rb +7 -7
  70. data/lib/morpheus/cli/commands/tasks.rb +2 -2
  71. data/lib/morpheus/cli/commands/vdi_pools_command.rb +6 -6
  72. data/lib/morpheus/cli/commands/view.rb +5 -1
  73. data/lib/morpheus/cli/commands/whitelabel_settings_command.rb +4 -4
  74. data/lib/morpheus/cli/commands/whoami.rb +2 -2
  75. data/lib/morpheus/cli/credentials.rb +30 -8
  76. data/lib/morpheus/cli/dot_file.rb +8 -15
  77. data/lib/morpheus/cli/error_handler.rb +16 -0
  78. data/lib/morpheus/cli/errors.rb +8 -1
  79. data/lib/morpheus/cli/mixins/print_helper.rb +17 -13
  80. data/lib/morpheus/cli/mixins/rest_command.rb +18 -18
  81. data/lib/morpheus/cli/mixins/secondary_rest_command.rb +12 -12
  82. data/lib/morpheus/cli/option_parser.rb +5 -1
  83. data/lib/morpheus/cli/option_types.rb +59 -12
  84. data/lib/morpheus/cli/version.rb +1 -1
  85. data/lib/morpheus/cli.rb +26 -16
  86. data/lib/morpheus/ext/rest_client.rb +3 -2
  87. data/lib/morpheus/formatters.rb +1 -1
  88. data/lib/morpheus/logging.rb +4 -4
  89. data/lib/morpheus/morpkg.rb +4 -4
  90. data/lib/morpheus/rest_client.rb +2 -2
  91. data/lib/morpheus/routes.rb +2 -2
  92. data/lib/morpheus/terminal.rb +65 -16
  93. data/lib/morpheus.rb +1 -1
  94. data/morpheus-cli.gemspec +1 -0
  95. data/test/api/containers_interface_test.rb +68 -0
  96. data/test/api/doc_interface_test.rb +35 -0
  97. data/test/api/instances_interface_test.rb +22 -0
  98. data/test/api/whoami_interface_test.rb +14 -0
  99. data/test/cli/access_token_test.rb +36 -0
  100. data/test/cli/auth_test.rb +82 -0
  101. data/test/cli/cli_test.rb +48 -0
  102. data/test/cli/containers_test.rb +92 -0
  103. data/test/cli/doc_test.rb +35 -0
  104. data/test/cli/help_test.rb +25 -0
  105. data/test/cli/instances_test.rb +36 -0
  106. data/test/cli/man_test.rb +14 -0
  107. data/test/cli/remote_test.rb +89 -0
  108. data/test/cli/roles_test.rb +34 -0
  109. data/test/cli/shell_test.rb +81 -0
  110. data/test/cli/version_test.rb +23 -0
  111. data/test/cli/view_test.rb +55 -0
  112. data/test/cli/whoami_test.rb +17 -0
  113. data/test/morpheus_test.rb +16 -0
  114. data/test/test_case.rb +338 -0
  115. data/test/test_config.rb +137 -0
  116. data/test/test_data_helper.rb +97 -0
  117. 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
@@ -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.2.1
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-11-23 00:00:00.000000000 Z
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