class Jamf::DistributionPoint

A FileShare Distribution Point in the JSS

As well as the normal Class and Instance methods for {APIObject} subclasses, the DistributionPoint class provides more interaction with other parts of the API.

Beyond the standard listing methods DistributionPoint.all, .all_ids, etc, every JSS has a single “master” distribution point. The Class method {DistributionPoint.master_distribution_point} will return the Jamf::DistributionPoint object for that master.

Also, some network segments have specific DistributionPoints assigned to them. Calling the Class method {DistributionPoint.my_distribution_point} will return a Jamf::DistributionPoint object for your local IP address.

Once you have an instance of Jamf::DistributionPoint, you can mount it (on a Mac) by calling its {#mount} method and unmount it with {#unmount}. The {Jamf::Package} and possibly {Jamf::Script} classes use this to upload items to the master.

NOTE: This class only deals with FileShare Distribution Points. There is no access to the Cloud Distribution Point in the classic API. See the .master_distribution_point and .my_distribution_point class methods for how they handle things when the Cloud DP is the master.

@see Jamf::APIObject

Constants

DEFAULT_MOUNTPOINT_DIR

Set default local mount for distribution point

DEFAULT_MOUNTPOINT_PREFIX
EMPTY_PW_256

An empty SHA256 digest

MOUNT_OPTIONS

what are the mount options? these are comma-separated, and are passed with -o

OBJECT_HISTORY_OBJECT_TYPE

the object type for this object in the object history table. See {APIObject#add_object_history_entry}

RSRC_BASE

The base for REST resources of this class

RSRC_LIST_KEY

the hash key used for the JSON list output of all objects in the JSS its also used in various error messages

RSRC_OBJECT_KEY

The hash key used for the JSON object output. It’s also used in various error messages

Attributes

certificate[R]

@return [String] the name of the cert. used for http cert. auth.

certificate_required[R]

@return [Boolean] do http downloads use cert. authentication?

connection_type[R]

@return [String] Protocol for fileservice access (e.g. AFP, SMB)

context[R]

@return [String] the “context” for http downloads (what goes after the hostname part of the URL)

enable_load_balancing[R]

@return [String] load balanacing enabled?

failover_point[R]

@return [Integer] the id of the DP to use for failover

failover_point_url[R]

@return [String] the URL to use if this one doesn’t work

hostname[R]

@return [String] the hostname of this DP

http_downloads_enabled[R]

@return [Boolean] are http downloads available from this DP?

http_password_sha256[R]

@return [String] the password for http downloads, if needed, as a SHA256 digest

http_url[R]

@return [String] the URL for http downloads

http_username[R]

@return [String] the username to use for http downloads if needed for user/pw auth

ip_address[R]

@return [String] the hostname of this DP

is_master[R]

@return [Boolean] is this the master DP?

local_path[R]

@return [String] the local path on the server to the distribution point directory

master?[R]

@return [Boolean] is this the master DP?

no_authentication_required[R]

@return [Boolean] do http downloads work without auth?

port[R]

@return [Integer] the port for http access

protocol[R]

@return [String] the protocol to use for http downloads (http/https)

read_only_password_sha256[R]

@return [String] read-only password as a SHA256 digest

read_only_username[R]

@return [String] read-only username for fileservice

read_write_password_sha256[R]

@return [String] the read-write password as a SHA256 digest

read_write_username[R]

@return [String] the read-write username for fileservice access

share_name[R]

@return [String] the name of the fileservice sharepoint

share_port[R]

@return [Integer] the port for fileservice access

ssh_password_sha256[R]

@return [String] the ssh password as a SHA256 digest

ssh_username[R]

@return [String] ssh username

username_password_required[R]

@return [Boolean] do http downloads use user/pw auth?

workgroup_or_domain[R]

@return [String] work group or domain for SMB

Public Class Methods

master_distribution_point(refresh = false, default: nil, api: nil, cnx: Jamf.cnx) click to toggle source

Get the DistributionPoint instance for the master distribution point.

If the Cloud Dist Point is master, then the classic API has no way to know that or access it. In that case you can provide the ‘default:’ parameter. Give it the name or id of any dist. point to be used instead, or give it :random to randomly choose one.

If there are no fileshare dist points defined (the cloud is the only one) then this whole class can’t really be used.

@param refresh should the distribution point be re-queried?

@param default[String, Integer, Symbol] Name or ID of a dist point to use

if no master is found, or :random to randomly choose one.

@param cnx [Jamf::Connection] which API connection should we query?

@return [Jamf::DistributionPoint]

    # File lib/jamf/api/classic/api_objects/distribution_point.rb
107 def self.master_distribution_point(refresh = false, default: nil, api: nil, cnx: Jamf.cnx)
108   cnx = api if api
109 
110   all_ids(refresh, cnx: cnx).each do |dp_id|
111     dp = fetch id: dp_id, cnx: cnx
112     return dp if dp.master?
113   end
114 
115   case default
116   when :random
117     fetch id: all_ids.sample, cnx: cnx
118   when nil
119     raise Jamf::NoSuchItemError, 'No Master FileShare Distribtion Point. Use the default: parameter if needed.'
120   else
121     fetch default, cnx: cnx
122   end
123 end
my_distribution_point(refresh = false, default: :master, api: nil, cnx: Jamf.cnx) click to toggle source

Get the DistributionPoint instance for the machine running this code, based on its IP address. If none is defined for this IP address, use the name or id provided as default. If no default: is provided, the master dp is used. If no master dp available (meaning its the cloud dp) then use a randomly chosen dp.

@param refresh should the distribution point be re-queried?

@param default[String, Integer, Symbol] the name or id of a Dist Point

to use if none is specified for this IP addr. Or :master, to use the
master DP, or :random to use a randomly chosen one. If :master is
specified and there is no master (master is cloud) then a random one is used.

@param cnx [Jamf::Connection] which API connection should we query?

@return [Jamf::DistributionPoint]

    # File lib/jamf/api/classic/api_objects/distribution_point.rb
142 def self.my_distribution_point(refresh = false, default: :master, api: nil, cnx: Jamf.cnx)
143   cnx = api if api
144 
145   @my_distribution_point = nil if refresh
146   return @my_distribution_point if @my_distribution_point
147 
148   my_net_seg_id = Jamf::NetworkSegment.my_network_segment refresh, cnx: cnx
149 
150   if my_net_seg_id
151     my_net_seg = Jamf::NetworkSegment.fetch id: my_net_seg_id, cnx: cnx
152     my_dp_name = my_net_seg.distribution_point
153     @my_distribution_point = fetch name: my_dp_name, cnx: cnx if my_dp_name
154   end # if my_net_seg_id
155 
156   return @my_distribution_point if @my_distribution_point
157 
158   @my_distribution_point =
159     case default
160     when String
161       fetch name: default, cnx: cnx
162     when Integer
163       fetch id: default, cnx: cnx
164     when :master
165       master_distribution_point refresh, default: :random, cnx: cnx
166     when :random
167       fetch id: all_ids(refresh).sample, cnx: cnx
168     end
169 end
new(**args) click to toggle source
Calls superclass method Jamf::APIObject::new
    # File lib/jamf/api/classic/api_objects/distribution_point.rb
261 def initialize(**args)
262   super
263 
264   @ip_address = @init_data[:ip_address]
265   @local_path = @init_data[:local_path]
266   @enable_load_balancing = @init_data[:enable_load_balancing]
267   @failover_point = @init_data[:failover_point]
268   @is_master = @init_data[:is_master]
269 
270   @connection_type = @init_data[:connection_type]
271   @share_port = @init_data[:share_port]
272   @share_name = @init_data[:share_name]
273   @workgroup_or_domain = @init_data[:workgroup_or_domain]
274 
275   @read_write_username = @init_data[:read_write_username]
276   @read_write_password_sha256 = @init_data[:read_write_password_sha256]
277   @read_only_username = @init_data[:read_only_username]
278   @read_only_password_sha256 = @init_data[:read_only_password_sha256]
279   @ssh_username = @init_data[:ssh_username]
280   @ssh_password_sha256 = @init_data[:ssh_password_sha256]
281   @http_username = @init_data[:http_username]
282   @http_password_sha256 = @init_data[:http_password_sha256]
283 
284   @http_downloads_enabled = @init_data[:http_downloads_enabled]
285   @protocol = @init_data[:protocol]
286   @port = @init_data[:port]
287   @context = @init_data[:context]
288   @no_authentication_required = @init_data[:no_authentication_required]
289   @certificate_required = @init_data[:certificate_required]
290   @username_password_required = @init_data[:username_password_required]
291   @certificate = @init_data[:certificate]
292   @http_url = @init_data[:http_url]
293   @failover_point_url = @init_data[:failover_point_url]
294 
295   @port = @init_data[:ssh_password]
296 
297   # if we mount for fileservice, where's the mountpoint?
298   @mountpoint = DEFAULT_MOUNTPOINT_DIR + "#{DEFAULT_MOUNTPOINT_PREFIX}#{@id}"
299 end

Public Instance Methods

check_pw(_user = nil, _pw = nil) click to toggle source

@deprecated The API no longer sends SHA256 hashed password data, and instead

only has a string of asterisks, meaning we can no longer use it to validate
passwords before attempting to use them. Instead, the processes that use
them, e.g. mounting a Dist. Point, will fail on their own if the pw is not
valid.

This method remains defined for backward-compatibility with any existing
code that calls it. but it will always return true. It will be removed in
a future version

@param user ignored

@param pw ignored

@return [TrueClass] Allow the process calling this to continue.

    # File lib/jamf/api/classic/api_objects/distribution_point.rb
317 def check_pw(_user = nil, _pw = nil)
318   true
319 end
mount(pw = nil, access = :ro) click to toggle source

Mount this distribution point locally.

@param pw the read-only or read-write password for this DistributionPoint

If :prompt, the user is promted on the commandline to enter the password for the :user.
If :stdin#, the password is read from a line of std in represented by the digits at #,
so :stdin3 reads the passwd from the third line of standard input. defaults to line 2,
if no digit is supplied. see {JSS.stdin}

@param access how to mount the DistributionPoint, and which password to expect.

:ro (or anything else) = read-only, :rw = read-write

@return [Pathname] the mountpoint.

    # File lib/jamf/api/classic/api_objects/distribution_point.rb
382 def mount(pw = nil, access = :ro)
383   return @mountpoint if mounted?
384 
385   access = :ro unless access == :rw
386 
387   password = if pw == :prompt
388                JSS.prompt_for_password "Enter the password for the #{access} user '#{access == :ro ? @read_only_username : @read_write_username}':"
389              elsif pw.is_a?(Symbol) && pw.to_s.start_with?('stdin')
390                pw.to_s =~ /^stdin(\d+)$/
391                line = Regexp.last_match(1)
392                line ||= 2
393                JSS.stdin line
394              else
395                pw
396              end
397 
398   username = access == :ro ? @read_only_username : @read_write_username
399 
400   safe_pw = CGI.escape password.to_s
401 
402   @mount_url = "#{@connection_type.downcase}://#{username}:#{safe_pw}@#{@ip_address}/#{@share_name}"
403   @mnt_cmd = case @connection_type.downcase
404              when 'smb' then '/sbin/mount_smbfs'
405              when 'afp' then '/sbin/mount_afp'
406              else raise "Can't mount distribution point #{@name}: no known connection type."
407              end
408 
409   @mountpoint.mkpath
410 
411   mount_out = `#{@mnt_cmd} -o '#{MOUNT_OPTIONS}' '#{@mount_url}' '#{@mountpoint}' 2>&1`
412   if ($CHILD_STATUS.exitstatus == 0) && @mountpoint.mountpoint?
413     # if system @mnt_cmd.to_s, *['-o', MOUNT_OPTIONS, @mount_url, @mountpoint.to_s]
414     @mounted = access
415   else
416     @mountpoint.rmdir if @mountpoint.directory?
417     @mounted = nil
418     raise Jamf::FileServiceError, "Can't mount #{@ip_address}: #{mount_out}"
419   end
420   @mountpoint
421 end
mounted?() click to toggle source

Is this thing mounted right now?

@return [Boolean]

    # File lib/jamf/api/classic/api_objects/distribution_point.rb
446 def mounted?
447   @mountpoint.directory? && @mountpoint.mountpoint?
448 end
reachable_for_download?(pw = '', check_http = true) click to toggle source

Check to see if this dist point is reachable for downloads (read-only) via either http, if available, or filesharing.

@param pw the read-only password to use for checking the connection

If http downloads are enabled, and no http password is required
this can be omitted.

@param check_http should we try the http download first, if enabled?

If you're intentionally using the ro password for filesharing, and want to check
only filesharing, then set this to false.

@return [FalseClass, Symbol] false if not reachable, otherwise :http or :mountable

    # File lib/jamf/api/classic/api_objects/distribution_point.rb
334 def reachable_for_download?(pw = '', check_http = true)
335   return :http if check_http && http_reachable?(pw)
336   return :mountable if mounted?
337 
338   begin
339     mount pw, :ro
340     :mountable
341   rescue
342     false
343   ensure
344     unmount
345   end
346 end
reachable_for_upload?(pw) click to toggle source

Check to see if this dist point is reachable for uploads (read-write) via filesharing.

@param pw the read-write password to use for checking the connection

@return [FalseClass, Symbol] false if not reachable, otherwise :mountable

    # File lib/jamf/api/classic/api_objects/distribution_point.rb
355 def reachable_for_upload?(pw)
356   return :mountable if mounted?
357 
358   begin
359     mount pw, :rw
360     :mountable
361   rescue
362     false
363   ensure
364     unmount
365   end
366 end
umount()
Alias for: unmount
unmount() click to toggle source

Unmount the distribution point.

Does nothing if it wasn’t mounted with mount.

@return [void]

    # File lib/jamf/api/classic/api_objects/distribution_point.rb
429 def unmount
430   return nil unless mounted?
431 
432   if system "/sbin/umount '#{@mountpoint}'"
433     sleep 1 # the umount takes time.
434     @mountpoint.rmdir if @mountpoint.directory? && !@mountpoint.mountpoint?
435     @mounted = false
436   else
437     raise Jamf::FileServiceError, "There was a problem unmounting #{@mountpoint}"
438   end
439   nil
440 end
Also aliased as: umount

Private Instance Methods

http_reachable?(pw = nil) click to toggle source

can the dp be reached for http downloads?

    # File lib/jamf/api/classic/api_objects/distribution_point.rb
460 def http_reachable?(pw = nil)
461   return false unless http_downloads_enabled
462 
463   url =
464     if @username_password_required
465       user_pass = "#{CGI.escape @http_username.to_s}:#{CGI.escape pw.to_s}@"
466       @http_url.sub "://#{@ip_address}", "://#{user_pass}#{@ip_address}"
467     else
468       @http_url
469     end
470   URI.parse(url).read
471   true
472 rescue
473   false
474 end
rest_xml() click to toggle source

Unused - until I get around to making DP’s updatable

the XML representation of the current state of this object, for POSTing or PUTting back to the JSS via the API Will be supported for Dist Points some day, I’m sure.

    # File lib/jamf/api/classic/api_objects/distribution_point.rb
482 def rest_xml
483   doc = REXML::Document.new
484   dp = doc.add_element 'distribution_point'
485   dp.add_element(:name.to_s).text = @name
486   dp.add_element(:ip_address.to_s).text = @ip_address
487   dp.add_element(:local_path.to_s).text = @local_path
488   dp.add_element(:enable_load_balancing.to_s).text = @enable_load_balancing
489   dp.add_element(:failover_point.to_s).text = @failover_point
490   dp.add_element(:is_master.to_s).text = @is_master
491 
492   dp.add_element(:connection_type.to_s).text = @connection_type
493   dp.add_element(:share_port.to_s).text = @share_port
494   dp.add_element(:share_name.to_s).text = @share_name
495   dp.add_element(:read_write_username.to_s).text = @read_write_username
496   dp.add_element(:read_write_password.to_s).text = @read_write_password
497   dp.add_element(:read_only_username.to_s).text = @read_only_username
498   dp.add_element(:read_only_password.to_s).text = @read_only_password
499   dp.add_element(:workgroup_or_domain.to_s).text = @workgroup_or_domain
500 
501   dp.add_element(:http_downloads_enabled.to_s).text = @http_downloads_enabled
502   dp.add_element(:protocol.to_s).text = @protocol
503   dp.add_element(:port.to_s).text = @port
504   dp.add_element(:context.to_s).text = @context
505   dp.add_element(:no_authentication_required.to_s).text = @no_authentication_required
506   dp.add_element(:certificate_required.to_s).text = @certificate_required
507   dp.add_element(:username_password_required.to_s).text = @username_password_required
508   dp.add_element(:http_username.to_s).text = @http_username
509   dp.add_element(:certificate.to_s).text = @certificate
510   dp.add_element(:http_url.to_s).text = @http_url
511   dp.add_element(:failover_point_url.to_s).text = @failover_point_url
512 
513   dp.add_element(:ssh_username.to_s).text = @ssh_username
514   dp.add_element(:ssh_password.to_s).text = @ssh_password if @ssh_password
515 
516   doc.to_s
517 end