Class: JSS::APIConnection
- Defined in:
- lib/jss.rb,
lib/jss/api_connection.rb
Overview
Instances of this class represent a REST connection to a JSS API.
For most cases, a single connection to a single JSS is all you need, and this is ruby-jss's default behavior.
If needed, multiple connections can be made and used sequentially or simultaneously.
Using the default connection
When ruby-jss is loaded, a not-yet-connected default instance of JSS::APIConnection is created and stored in the constant JSS::API. This connection is used as the initial 'active connection' (see below) so all methods that make API calls will use it by default. For most uses, where you're only going to be working with one connection to one JSS, the default connection is all you need.
Before using it you must call its #connect method, passing in appropriate connection details and credentials.
Example:
require 'ruby-jss'
JSS.api.connect server: 'server.address.edu', user: 'jss-api-user', pw: :prompt
# (see {JSS::APIConnection#connect} for all the connection options)
a_phone = JSS::MobileDevice.fetch id: 8743
# the mobile device was fetched through the default connection
Using Multiple Simultaneous Connections
Sometimes you need to connect simultaneously to more than one JSS. or to the same JSS with different credentials. ruby-jss allows you to create as many connections as needed, and gives you three ways to use them:
-
Making a connection 'active', after which API calls go thru it automatically
Example:
a_computer = JSS::Computer.fetch id: 1234 # the JSS::Computer with id 1234 is fetched from the active connection # and stored in the variable 'a_computer'
NOTE: When ruby-jss is first loaded, the default connection (see above) is the active connection.
-
Passing an APIConnection instance to methods that use the API
Example:
a_computer = JSS::Computer.fetch id: 1234, api: production_api # the JSS::Computer with id 1234 is fetched from the connection # stored in the variable 'production_api'. The computer is # then stored in the variable 'a_computer'
-
Using the APIConnection instance itself to make API calls.
Example:
a_computer = production_api.fetch :Computer, id: 1234 # the JSS::Computer with id 1234 is fetched from the connection # stored in the variable 'production_api'. The computer is # then stored in the variable 'a_computer'
See below for more details about the ways to use multiple connections.
NOTE: Objects retrieved or created through an APIConnection store an internal reference to that APIConnection and use that when they make other API calls, thus ensuring data consistency when using multiple connections.
Similiarly, the data caches used by APIObject list methods (e.g. JSS::Computer.all, .all_names, and so on) are stored in the APIConnection instance through which they were read, so they won't be incorrect when you use multiple connections.
Making new APIConnection instances
New connections can be created using the standard ruby 'new' method.
If you provide connection details when calling 'new', they will be passed to the #connect method immediately. Otherwise you can call #connect later.
production_api = JSS::APIConnection.new(
name: 'prod',
server: 'prodserver.address.org',
user: 'produser',
pw: :prompt
)
# the new connection is now stored in the variable 'production_api'.
Using the 'Active' Connection
While multiple connection instances can be created, only one at a time is 'the active connection' and all APIObject-based access methods in ruby-jss will use it automatically. When ruby-jss is loaded, the default connection (see above) is the active connection.
To use the active connection, just call a method on an APIObject subclass that uses the API.
For example, the various list methods:
all_computer_sns = JSS::Computer.all_serial_numbers
# the list of all computer serial numbers is read from the active
# connection and stored in all_computer_sns
Fetching an object from the API:
victim_md = JSS::MobileDevice.fetch id: 832
# the variable 'victim_md' now contains a JSS::MobileDevice queried
# through the active connection.
The currently-active connection instance is available from the `JSS.api` method.
Making a Connection Active
Only one connection is 'active' at a time and the currently active one is returned when you call `JSS.api` or its alias `JSS.active_connection`
To activate another connection just pass it to the JSS.use_api method like so:
JSS.use_api production_api
# the connection we stored in 'production_api' is now active
To re-activate to the default connection, just call
JSS.use_default_connection
Connection Names:
As seen in the example above, you can provide a 'name:' parameter (a String or a Symbol) when creating a new connection. The name can be used later to identify connection objects.
If you don't provide one, the name is ':disconnected' until you connect, and then 'user@server:port' after connecting.
The name of the default connection is always :default
To see the name of the currently active connection, just use `JSS.api.name`
JSS.use_api production_api
JSS.api.name # => 'prod'
JSS.use_default_connection
JSS.api.name # => :default
Creating, Storing and Activating a connection in one step
Both of the above steps (creating/storing a connection, and making it active) can be performed in one step using the `JSS.new_api_connection` method, which creates a new APIConnection, makes it the active connection, and returns it.
production_api2 = JSS.new_api_connection(
name: 'prod2',
server: 'prodserver.address.org',
user: 'produser',
pw: :prompt
)
JSS.api.name # => 'prod2'
Passing an APIConnection object to API-related methods
All methods that use the API can take an 'api:' parameter which contains an APIConnection object. When provided, that APIconnection is used rather than the active connection.
For example:
prod2_computer_sns = JSS::Computer.all_serial_numbers, api: production_api2
# the list of all computer serial numbers is read from the connection in
# the variable 'production_api2' and stored in 'prod2_computer_sns'
prod2_victim_md = JSS::MobileDevice.fetch id: 832, api: production_api2
# the variable 'prod2_victim_md' now contains a JSS::MobileDevice queried
# through the connection 'production_api2'.
Low-level use of APIConnection instances.
For most cases, using APIConnection instances as mentioned above is all you'll need. However to access API resources that aren't yet implemented in other parts of ruby-jss, you can use the methods #get_rsrc, #put_rsrc, #post_rsrc, & #delete_rsrc documented below.
For even lower-level work, you can access the underlying Faraday::Connection inside the APIConnection via the connection's #cnx attribute.
APIConnection instances also have a #server attribute which contains an instance of Server q.v., representing the JSS to which it's connected.
Constant Summary collapse
- RSRC_BASE =
The base API path in the jss URL
'JSSResource'.freeze
- TEST_PATH =
A url path to load to see if there's an API available at a host. This just loads the API resource docs page
"#{RSRC_BASE}/accounts".freeze
- TEST_CONTENT =
If the test path loads correctly from a casper server, it'll contain this text (this is what we get when we make an unauthenticated API call.)
'<p>The request requires user authentication</p>'.freeze
- HTTP_PORT =
The Default port
9006
- SSL_PORT =
The Jamf default SSL port, default for locally-hosted servers
8443
- HTTPS_SSL_PORT =
The https default SSL port, default for Jamf Cloud servers
443
- SSL_PORTS =
if either of these is specified, we'll default to SSL
[SSL_PORT, HTTPS_SSL_PORT].freeze
- JAMFCLOUD_DOMAIN =
Recognize Jamf Cloud servers
'jamfcloud.com'.freeze
- JAMFCLOUD_PORT =
JamfCloud connections default to 443, not 8443
HTTPS_SSL_PORT
- XML_HEADER =
The top line of an XML doc for submitting data via API
'<?xml version="1.0" encoding="UTF-8" standalone="no"?>'.freeze
- DFT_OPEN_TIMEOUT =
Default timeouts in seconds
60
- DFT_TIMEOUT =
60
- DFT_SSL_VERSION =
The Default SSL Version
'TLSv1_2'.freeze
- RSRC_NOT_FOUND_MSG =
'The requested resource was not found'.freeze
- EXTENDABLE_CLASSES =
These classes are extendable, and may need cache flushing for EA definitions
[JSS::Computer, JSS::MobileDevice, JSS::User].freeze
- GET_FORMATS =
values for the format param of get_rsrc
%i[json xml].freeze
- HTTP_ACCEPT_HEADER =
'Accept'.freeze
- HTTP_CONTENT_TYPE_HEADER =
'Content-Type'.freeze
- MIME_JSON =
'application/json'.freeze
- MIME_XML =
'application/xml'.freeze
Instance Attribute Summary collapse
-
#cnx ⇒ Faraday::Connection
readonly
The underlying connection resource.
-
#connected ⇒ Boolean
(also: #connected?)
readonly
Are we connected right now?.
-
#ext_attr_definition_cache ⇒ Hash{Class: Hash{String => JSS::ExtensionAttribute}}
readonly
This Hash caches the Extension Attribute definition objects for the three types of ext.
-
#last_http_response ⇒ Faraday::Response
readonly
The response from the most recent API call.
-
#name ⇒ String, Symbol
readonly
connection during initialization, using the name: parameter.
-
#object_list_cache ⇒ Hash
readonly
This Hash caches the result of the the first API query for an APIObject subclass's .all summary list, keyed by the subclass's RSRC_LIST_KEY.
-
#port ⇒ Integer
readonly
The port used for the connection.
-
#protocol ⇒ String
readonly
The protocol being used: http or https.
-
#rest_url ⇒ String
readonly
The base URL to to the current REST API.
-
#server ⇒ JSS::Server
readonly
The details of the JSS to which we're connected.
-
#server_host ⇒ String
readonly
The hostname of the JSS to which we're connected.
-
#server_path ⇒ String
readonly
Any path in the URL below the hostname.
-
#user ⇒ String
(also: #jss_user)
readonly
The username who's connected to the JSS API.
Instance Method Summary collapse
-
#connect(args = {}) ⇒ true
Connect to the JSS Classic API.
-
#delete_rsrc(rsrc) ⇒ String
Delete a resource from the JSS.
-
#disconnect ⇒ void
With a REST connection, there isn't any real “connection” to disconnect from So to disconnect, we just unset all our credentials.
-
#flushcache(key = nil) ⇒ void
Empty all cached lists from this connection then run garbage collection to clear any available memory.
-
#get_rsrc(rsrc, format = :json, raw_json: false) ⇒ Hash, String
Get a JSS resource The first argument is the resource to get (the part of the API url after the 'JSSResource/' ) The resource must be properly URL escaped beforehand.
-
#hostname ⇒ String
(also: #host)
The server to which we are connected, or will try connecting to if none is specified with the call to #connect.
-
#initialize(args = {}) ⇒ APIConnection
constructor
If name: is provided (as a String or Symbol) that will be stored as the APIConnection's name attribute.
-
#open_timeout=(timeout) ⇒ void
Reset the open-connection timeout for the rest connection.
-
#post_rsrc(rsrc, xml) ⇒ String
Create a new JSS resource.
-
#pretty_print_instance_variables ⇒ Array
Remove the various cached data from the instance_variables used to create pretty-print (pp) output.
-
#put_rsrc(rsrc, xml) ⇒ String
Update an existing JSS resource.
-
#timeout=(timeout) ⇒ void
Reset the response timeout for the rest connection.
-
#to_s ⇒ String
A useful string about this connection.
-
#upload(rsrc, local_file) ⇒ String
Upload a file.
-
#valid_server?(server, port = SSL_PORT) ⇒ Boolean
Test that a given hostname & port is a JSS API server.
Constructor Details
#initialize(args = {}) ⇒ APIConnection
385 386 387 388 389 390 391 |
# File 'lib/jss/api_connection.rb', line 385 def initialize(args = {}) @name = args.delete :name @name ||= :unknown @connected = false @object_list_cache = {} connect args unless args.empty? end |
Instance Attribute Details
#cnx ⇒ Faraday::Connection (readonly)
Returns the underlying connection resource.
299 300 301 |
# File 'lib/jss/api_connection.rb', line 299 def cnx @cnx end |
#connected ⇒ Boolean (readonly) Also known as: connected?
Returns are we connected right now?.
302 303 304 |
# File 'lib/jss/api_connection.rb', line 302 def connected @connected end |
#ext_attr_definition_cache ⇒ Hash{Class: Hash{String => JSS::ExtensionAttribute}} (readonly)
This Hash caches the Extension Attribute definition objects for the three types of ext. attribs: ComputerExtensionAttribute, MobileDeviceExtensionAttribute, and UserExtensionAttribute, whenever they are fetched for parsing or validating extention attribute data.
The top-level keys are the EA classes themselves:
-
ComputerExtensionAttribute
-
MobileDeviceExtensionAttribute
-
UserExtensionAttribute
These each point to a Hash of their instances, keyed by name, e.g.
{
"A Computer EA" => <JSS::ComputerExtensionAttribute...>,
"A different Computer EA" => <JSS::ComputerExtensionAttribute...>,
...
}
370 371 372 |
# File 'lib/jss/api_connection.rb', line 370 def ext_attr_definition_cache @ext_attr_definition_cache end |
#last_http_response ⇒ Faraday::Response (readonly)
Returns The response from the most recent API call.
321 322 323 |
# File 'lib/jss/api_connection.rb', line 321 def last_http_response @last_http_response end |
#name ⇒ String, Symbol (readonly)
connection during initialization, using the name: parameter. defaults to user@hostname:port
329 330 331 |
# File 'lib/jss/api_connection.rb', line 329 def name @name end |
#object_list_cache ⇒ Hash (readonly)
This Hash caches the result of the the first API query for an APIObject subclass's .all summary list, keyed by the subclass's RSRC_LIST_KEY. See the APIObject.all class method.
It also holds related data items for speedier processing:
-
The Hashes created by APIObject.map_all_ids_to(foo), keyed by “#RSRC_LIST_KEYmap#other_key”.to_sym
-
This hash also holds a cache of the rarely-used APIObject.all_objects hash, keyed by “#RSRC_LIST_KEY_objects”.to_sym
When APIObject.all, and related methods are called without an argument, and this hash has a matching value, the value is returned, rather than requerying the API. The first time a class calls .all, or whnever refresh is not false, the API is queried and the value in this hash is updated.
349 350 351 |
# File 'lib/jss/api_connection.rb', line 349 def object_list_cache @object_list_cache end |
#port ⇒ Integer (readonly)
Returns the port used for the connection.
315 316 317 |
# File 'lib/jss/api_connection.rb', line 315 def port @port end |
#protocol ⇒ String (readonly)
Returns the protocol being used: http or https.
318 319 320 |
# File 'lib/jss/api_connection.rb', line 318 def protocol @protocol end |
#rest_url ⇒ String (readonly)
Returns The base URL to to the current REST API.
324 325 326 |
# File 'lib/jss/api_connection.rb', line 324 def rest_url @rest_url end |
#server ⇒ JSS::Server (readonly)
Returns the details of the JSS to which we're connected.
306 307 308 |
# File 'lib/jss/api_connection.rb', line 306 def server @server end |
#server_host ⇒ String (readonly)
Returns the hostname of the JSS to which we're connected.
309 310 311 |
# File 'lib/jss/api_connection.rb', line 309 def server_host @server_host end |
#server_path ⇒ String (readonly)
Returns any path in the URL below the hostname. See #connect.
312 313 314 |
# File 'lib/jss/api_connection.rb', line 312 def server_path @server_path end |
#user ⇒ String (readonly) Also known as: jss_user
Returns the username who's connected to the JSS API.
295 296 297 |
# File 'lib/jss/api_connection.rb', line 295 def user @user end |
Instance Method Details
#connect(args = {}) ⇒ true
Connect to the JSS Classic API.
430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 |
# File 'lib/jss/api_connection.rb', line 430 def connect(args = {}) # new connections always get new caches flushcache args[:no_port_specified] = args[:port].to_s.empty? args = apply_connection_defaults args @timeout = args[:timeout] @open_timeout = args[:open_timeout] # ensure an integer args[:port] &&= args[:port].to_i # confirm we know basics verify_basic_args args # parse our ssl situation verify_ssl args @user = args[:user] @rest_url = build_rest_url args # figure out :password from :pw args[:password] = acquire_password args # heres our connection @cnx = create_connection args[:password] verify_server_version @name = "#{@user}@#{@server_host}:#{@port}" if @name.nil? || @name == :disconnected @connected ? hostname : nil end |
#delete_rsrc(rsrc) ⇒ String
Delete a resource from the JSS
648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 |
# File 'lib/jss/api_connection.rb', line 648 def delete_rsrc(rsrc) validate_connected raise MissingDataError, 'Missing :rsrc' if rsrc.nil? # delete the resource @last_http_response = @cnx.delete(rsrc) do |req| req.headers[HTTP_CONTENT_TYPE_HEADER] = MIME_XML req.headers[HTTP_ACCEPT_HEADER] = MIME_XML end unless @last_http_response.success? handle_http_error return end @last_http_response.body end |
#disconnect ⇒ void
This method returns an undefined value.
With a REST connection, there isn't any real “connection” to disconnect from So to disconnect, we just unset all our credentials.
497 498 499 500 501 502 503 |
# File 'lib/jss/api_connection.rb', line 497 def disconnect @user = nil @rest_url = nil @server_host = nil @cnx = nil @connected = false end |
#flushcache(key = nil) ⇒ void
This method returns an undefined value.
Empty all cached lists from this connection then run garbage collection to clear any available memory
If an APIObject Subclass's RSRC_LIST_KEY is specified, only the caches for that class are flushed (e.g. :computers, :comptuer_groups)
NOTE if you've referenced objects in these caches, those objects won't be removed from memory, but all cached data will be recached as needed.
715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 |
# File 'lib/jss/api_connection.rb', line 715 def flushcache(key = nil) if EXTENDABLE_CLASSES.include? key @ext_attr_definition_cache[key] = {} elsif key map_key_pfx = "#{key}_map_" @object_list_cache.delete_if do |cache_key, _cache| cache_key == key || cache_key.to_s.start_with?(map_key_pfx) end @ext_attr_definition_cache else @object_list_cache = {} @ext_attr_definition_cache = {} end GC.start end |
#get_rsrc(rsrc, format = :json, raw_json: false) ⇒ Hash, String
Get a JSS resource The first argument is the resource to get (the part of the API url after the 'JSSResource/' ) The resource must be properly URL escaped beforehand. Note: URL.encode is deprecated, use CGI.escape
By default we get the data in JSON, and parse it into a ruby Hash with symbolized Hash keys.
If the second parameter is :xml then the XML version is retrieved and returned as a String.
To get the raw JSON string as it comes from the API, pass raw_json: true
529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 |
# File 'lib/jss/api_connection.rb', line 529 def get_rsrc(rsrc, format = :json, raw_json: false) validate_connected raise JSS::InvalidDataError, 'format must be :json or :xml' unless GET_FORMATS.include? format @last_http_response = @cnx.get(rsrc) do |req| req.headers[HTTP_ACCEPT_HEADER] = format == :json ? MIME_JSON : MIME_XML end unless @last_http_response.success? handle_http_error return end return JSON.parse(@last_http_response.body, symbolize_names: true) if format == :json && !raw_json @last_http_response.body end |
#hostname ⇒ String Also known as: host
The server to which we are connected, or will try connecting to if none is specified with the call to #connect
690 691 692 693 694 695 696 |
# File 'lib/jss/api_connection.rb', line 690 def hostname return @server_host if @server_host srvr = JSS::CONFIG.api_server_name srvr ||= JSS::Client.jss_server srvr end |
#open_timeout=(timeout) ⇒ void
This method returns an undefined value.
Reset the open-connection timeout for the rest connection
488 489 490 |
# File 'lib/jss/api_connection.rb', line 488 def open_timeout=(timeout) @cnx.[:open_timeout] = timeout end |
#post_rsrc(rsrc, xml) ⇒ String
Create a new JSS resource
585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 |
# File 'lib/jss/api_connection.rb', line 585 def post_rsrc(rsrc, xml) validate_connected # convert CRs & to xml&.gsub!(/\r/, ' ') # send the data @last_http_response = @cnx.post(rsrc) do |req| req.headers[HTTP_CONTENT_TYPE_HEADER] = MIME_XML req.headers[HTTP_ACCEPT_HEADER] = MIME_XML req.body = xml end unless @last_http_response.success? handle_http_error return end @last_http_response.body end |
#pretty_print_instance_variables ⇒ Array
Remove the various cached data from the instance_variables used to create pretty-print (pp) output.
738 739 740 741 742 743 744 745 746 747 |
# File 'lib/jss/api_connection.rb', line 738 def pretty_print_instance_variables vars = instance_variables.sort vars.delete :@object_list_cache vars.delete :@last_http_response vars.delete :@network_ranges vars.delete :@my_distribution_point vars.delete :@master_distribution_point vars.delete :@ext_attr_definition_cache vars end |
#put_rsrc(rsrc, xml) ⇒ String
Update an existing JSS resource
556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 |
# File 'lib/jss/api_connection.rb', line 556 def put_rsrc(rsrc, xml) validate_connected # convert CRs & to xml.gsub!(/\r/, ' ') # send the data @last_http_response = @cnx.put(rsrc) do |req| req.headers[HTTP_CONTENT_TYPE_HEADER] = MIME_XML req.headers[HTTP_ACCEPT_HEADER] = MIME_XML req.body = xml end unless @last_http_response.success? handle_http_error return end @last_http_response.body end |
#timeout=(timeout) ⇒ void
This method returns an undefined value.
Reset the response timeout for the rest connection
478 479 480 |
# File 'lib/jss/api_connection.rb', line 478 def timeout=(timeout) @cnx.[:timeout] = timeout end |
#to_s ⇒ String
A useful string about this connection
468 469 470 |
# File 'lib/jss/api_connection.rb', line 468 def to_s @connected ? "Using #{@rest_url} as user #{@user}" : 'not connected' end |
#upload(rsrc, local_file) ⇒ String
Upload a file. This is really only used for the 'fileuploads' endpoint, as implemented in the Uploadable mixin module, q.v.
616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 |
# File 'lib/jss/api_connection.rb', line 616 def upload(rsrc, local_file) validate_connected # the upload file object for faraday local_file = Pathname.new local_file upfile = Faraday::UploadIO.new( local_file.to_s, 'application/octet-stream', local_file.basename.to_s ) # send it and get the response @last_http_response = @cnx.post rsrc do |req| req.headers['Content-Type'] = 'multipart/form-data' req.body = { name: upfile } end unless @last_http_response.success? handle_http_error return false end true end |
#valid_server?(server, port = SSL_PORT) ⇒ Boolean
Test that a given hostname & port is a JSS API server
675 676 677 678 679 680 681 682 |
# File 'lib/jss/api_connection.rb', line 675 def valid_server?(server, port = SSL_PORT) # cheating by shelling out to curl, because getting open-uri, or even net/http to use # ssl_options like :OP_NO_SSLv2 and :OP_NO_SSLv3 will take time to figure out.. return true if `/usr/bin/curl -s 'https://#{server}:#{port}/#{TEST_PATH}'`.include? TEST_CONTENT return true if `/usr/bin/curl -s 'http://#{server}:#{port}/#{TEST_PATH}'`.include? TEST_CONTENT false end |