#!/usr/bin/env ruby ### Copyright 2023 Pixar ### ### Licensed under the Apache License, Version 2.0 (the "Apache License") ### with the following modification; you may not use this file except in ### compliance with the Apache License and the following modification to it: ### Section 6. Trademarks. is deleted and replaced with: ### ### 6. Trademarks. This License does not grant permission to use the trade ### names, trademarks, service marks, or product names of the Licensor ### and its affiliates, except as required to comply with Section 4(c) of ### the License and to reproduce the content of the NOTICE file. ### ### You may obtain a copy of the Apache License at ### ### http://www.apache.org/licenses/LICENSE-2.0 ### ### Unless required by applicable law or agreed to in writing, software ### distributed under the Apache License with the above modification is ### distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY ### KIND, either express or implied. See the Apache License for the specific ### language governing permissions and limitations under the Apache License. ### ### require 'pathname' require 'getoptlong' require 'ruby-jss' # Debug Trace support, Ideas from # https://gist.github.com/bendilley/1453a555e981ed533f68 # and the other gist and stack overflow mentioned there. #################################### # Enable tracing to help debug stack level too deep errors $do_tracing = false # set this to true to save the call traces to a file $save_trace = false # the file to hold the traces $trace_out = open('/tmp/ruby-jss-tests-trace.txt', 'w') # the max call-stack depth - adjust this to help catch and # debug SystemStackError exceptions $trace_max_call_stack_depth = 1000 # the tracing proc set_trace_func proc { |event, file, line, id, _binding, classname| if $do_tracing begin if event == 'call' $trace_out.puts "#{file}:#{line} #{classname}##{id}" if $save_trace raise 'MaxCallStackReached' if caller_locations.length > $trace_max_call_stack_depth end rescue => e warn "ERROR: #{e.class}: #{e}" e.backtrace.each { |l| warn "..#{l}" } exit 1 end end # if } # app class App TEST_MODULE_FILE_SUFFIX = '.rb' # Server and port come from CLI opts or ruby-jss.conf # # User comes from keychain, or prompt when saving credentials for host # # pw come from keychain, or is prompted and stored # in keychain with user. # # TODO: support for other APIConnection options # OPTS = GetoptLong.new( ['--host', '-h', GetoptLong::REQUIRED_ARGUMENT], ['--port', '-p', GetoptLong::REQUIRED_ARGUMENT], ['--re-save', '-r', GetoptLong::REQUIRED_ARGUMENT], ['--help', '-H', GetoptLong::NO_ARGUMENT] ) # Setup def initialize raise "These tests only work in ruby-jss 2.0 and up. You are running #{JSS::VERSION}" unless JSS::VERSION[0].to_i > 1 parse_opts @bindir = Pathname.new File.dirname(__FILE__) @appdir = @bindir.parent @libdir = @appdir + 'lib' @testsdir = @appdir + 'tests' @tests_to_run = ARGV.dup @tests_to_run = @testsdir.children.map { |c| c.basename.to_s }.sort if @tests_to_run.empty? @tests_to_run.delete_if { |t| t.start_with? '.' } @minitest_opts = ['--verbose'] helper_module = @libdir + 'jamf_test.rb' load helper_module.to_s end # init # Parse ARGV def parse_opts OPTS.each do |opt, arg| case opt when '--host' @api_server = arg when '--port' @api_port = arg when '--re-save' @re_save_creds = true when '--gem-dir' @custom_gem_dir = arg when '--help' @show_help = true end # case end # opts.each end ####################### def run return if show_help raise ArgumentError, 'A server hostname must be provided with --host' unless @api_server return unless prod_server_confirmed_if_needed? connect run_tests end # if @api_server == matches the one in /etc/ruby-jss.conf # get confirmation before running tests on the production server. # If a local ~/.ruby-jss.conf defines a diferent server, # or a different server if given with --server, no confirmation is needed def prod_server_confirmed_if_needed? conf = Pathname.new '/etc/ruby-jss.conf' return true unless conf.file? prod_server_line = conf.readlines.select { |l| l.start_with? 'api_server_name' }.first return true unless prod_server_line prod_server = prod_server_line.chomp.split(': ').last.strip return true unless prod_server == @api_server print "Really run the tests on the production server '#{prod_server}'? (y/n): " doit = $stdin.gets.chomp doit == 'y' end ################################### def connect JamfTest::Auth.delete_api_creds(@api_server) if @re_save_creds JamfTest::Auth.connect_to_api host: @api_server, port: @api_port end ################################### def run_tests puts JamfTest.say "Starting tests of ruby-jss v#{Jamf::VERSION}, using ruby version #{RUBY_VERSION}" JamfTest.say "API Connection: #{Jamf.cnx}; Server running Jamf Pro #{Jamf.cnx.jamf_version}" @tests_to_run.each do |t| t = t.chomp TEST_MODULE_FILE_SUFFIX testfile = @testsdir + "#{t}#{TEST_MODULE_FILE_SUFFIX}" unless testfile.file? JamfTest.say "Skipping unknown test #{testfile.basename}" next end load testfile.to_s test_class = JamfTest.const_get(camelize_snake(t)) puts JamfTest.say "Running test #{testfile.basename}: " test_class.new.run_tests rescue => e puts "ERROR in '#{testfile.basename}': #{e.class}: #{e}" puts e.backtrace end # each end # run tests ################################### def camelize_snake(str) str.to_s.split('_').map(&:capitalize).join end # display help if asked ################################### def show_help return false unless @show_help puts <<-USAGEBLURB Usage: #{File.basename __FILE__} [options] [spec [spec ...]] Runs one or more specification tests of ruby-jss. The specifications are files in the directory: #{@specdir} The files must have a _spec.rb suffix, however you need not use the suffix when listing tests to run on the command line, e.g. 'patch_source' will run 'patch_source_spec.rb' If no specs files are listed on the command line, all will be run, in alphabetical order. By default, JSS connection settings are used from your /etc/ruby-jss.conf file and/or ~/.ruby-jss.conf. Connection settings from the command line will be used if provided. WARNING: These tests create, modify, and delete objects in the JSS. While no existing objects should be changed, * Be Careful * running them on a production server. If the server you're connecting to matches one defined in /etc/ruby-jss.conf you will be asked for confirmation before proceding. The first time you connect from this machine to a given server, you must provide a username for the connection with --user, and will be prompted for the password. Once authenticated, credentials for the server are saved in your keychain, and future connections to that server will read the user & password from there. If a different user is later specified for that server, you'll be prompted again for a password, and the keychain will be updated. Options --host, -h The hostname for the JSS API connection --port, -p The port for the API connection --re-save, -r Prompt for user and passwd for this host, even if already saved in the keychain --gem-dir, -g, The path from which to require ruby-jss --help, -h, -H Show this help USAGEBLURB true end # show_help end # class app ################# The main block ######################## if $PROGRAM_NAME == __FILE__ begin app = App.new app.run exit 0 rescue SystemStackError puts $! puts caller[0..100] exit 1 rescue => e puts "ERROR #{e.class}: #{e}" puts e.backtrace exit 1 end # begin end # if $0 == __FILE__