Class: D3::Package

Inherits:
JSS::Package
  • Object
show all
Includes:
Basename, Validate
Defined in:
lib/d3/package.rb,
lib/d3/package/mixins.rb,
lib/d3/package/aliases.rb,
lib/d3/package/getters.rb,
lib/d3/package/setters.rb,
lib/d3/package/validate.rb,
lib/d3/package/constants.rb,
lib/d3/package/questions.rb,
lib/d3/package/attributes.rb,
lib/d3/package/constructor.rb,
lib/d3/package/class_methods.rb,
lib/d3/package/client_actions.rb,
lib/d3/package/server_actions.rb,
lib/d3/package/class_variables.rb,
lib/d3/package/private_methods.rb

Overview

Package - A JSS::Package that can be installed and maintained by d3

Defined Under Namespace

Modules: Validate

Constant Summary collapse

P_TABLE =

A short name for the d3 packages table definition

D3::Database::PACKAGE_TABLE
P_FIELDS =

A short name for the basename table field definitions

P_TABLE[:field_definitions]
SCRIPT_TYPES =

the possible types of scripts associated with a d3 pkg

[:pre_install, :post_install, :pre_remove, :post_remove]
DFT_EXPIRATION_DAYS =

the default expiration is to never expire

0
PKG_TYPES =

These are the kinds of pkgs we can deal wth note that :pkg means all flavors, including .mpkg’s

[:pkg, :dmg]
PKG_CONTENTS_TABLE =

d3 allows indexing, so here’s the MySQL table that holds the package indices

"package_contents"
PKG_RE =

This regular expression matches valid (m)pkg filenames

/\.m?pkg$/
DEFAULT_STATUS =

default status is :pilot

:pilot
@@package_data =

this holds the data from the D3::Database::Packages table in the db See D3::Package.package_data

nil
@@all_packages =

This holds all D3::Package objects See D3::Package.all

nil
@@filenames =

this holds all the pkg filesnames known to the jss

nil

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(args = {}) ⇒ Package

Existing d3 pkgs are looked up by providing :id, :name, :basename, :edition, or the combination of :basename, :version, and :revision (which comprise the edition)

If passed only a :basename, the currently-live package for that basename

is used, an exception is raised if no version of the basename is live.

When creating a new d3 package use :id => :new, as for JSS::Package. You must provide :name, :basename, :version, and :revision.

To add a pkg to d3 that’s already in the JSS, use import (q.v.)

For new (or imported) packages, you may also provide any of the other

data keys mentioned in P_FIELDS.keys and they will be applied to the
new Package. You may also set them after instantiation using their
respective setter methods. A value for :admin must be set before
calling {#create}.


50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
# File 'lib/d3/package/constructor.rb', line 50

def initialize (args={})

  # refresh our pkg data first
  D3::Package.package_data :refresh

  # if we didn't get an edition, did we get the parts?
  if args[:basename] && args[:version] && args[:revision]
    args[:edition] ||= "#{args[:basename]}-#{args[:version]}-#{args[:revision]}"
  end
  args[:category] ||= D3::CONFIG.jss_default_pkg_category

  ############ Adding a New d3/jss package
  if args[:id] == :new

    # make sure we have the needed args
    unless args[:basename] and args[:version] and args[:revision]
      raise JSS::MissingDataError, "New d3 packages need :basename, :version, & :revision."
    end

    # does the edition we're creating already exist?
    if D3::Package.all_editions.include? args[:edition]
      raise JSS::AlreadyExistsError, "Package edition #{args[:edition]} already exists in d3"
    end

    @adding = true

  ############ Importing an existing JSS pkg?
  elsif args[:import]

    # args[:import] should only ever come from D3::Package.import
    # in ruby 1.8 use caller[1][/`([^']*)'/, 1] to get the label 'import'
    # doesn't matter since JSS now requires ruby 1.9.2
    raise JSS::InvalidDataError, "Use D3::Package.import to import existing JSS packages to d3." unless caller_locations(2,1)[0].label == "import"


    # data checking was done in the import class method
    @importing = true

  ############ Looking up an existing package by id, name, basename, or edition
  else
    if args[:id]
      status =  D3::Package.statuses_by(:id)[args[:id]]
      if status
        @status = :missing if status == :missing
      else
        raise JSS::NoSuchItemError, "No package in d3 with id: #{args[:id]}"
      end

    elsif args[:name]
      status =  D3::Package.statuses_by(:name)[args[:name]]
      if status
        @status = :missing if status == :missing
        args[:id] = JSS::Package.map_all_ids_to(:name).invert[args[:name]]
      else
        raise JSS::NoSuchItemError, "No package in d3 with name: #{args[:name]}"
      end


    elsif args[:edition]
      status =  D3::Package.statuses_by(:edition)[args[:edition]]
      if status
        @status = :missing if status == :missing
        args[:id] = D3::Package.ids_to_editions.invert[ args[:edition]]
      else
        raise JSS::NoSuchItemError, "No package in d3 with edition: #{args[:edition]}"
      end


    elsif args[:basename]
      args[:id] = D3::Package.basenames_to_live_ids[args[:basename]]
      raise JSS::NoSuchItemError, "No live package for basename '#{args[:basename]}'" unless args[:id]
    end # if args :id

    @lookup_existing = true

  end # if args[:id] == :new

  # if the pkg is missing from the jss, there's nothing to do below here
  return if @status == :missing

  # now we have an :id (which might be :new) so let JSS::Package do its work
  # this will tie us to a new or existing jss pkg
  super args

  # does this pkg need to be added to d3?
  if @adding or @importing

    d3pkg_data = args
    @status = :unsaved
    @in_d3 = false

  else # package already exists in both JSS and d3...

    # This prevents some checks from happening, since the data came from the DB
    @initializing = true
    d3pkg_data = D3::Package.package_data(:refresh)[@id]
    @in_d3 = true

  end # if  @adding or @importing

  @basename = d3pkg_data[:basename]
  @version = d3pkg_data[:version]
  @revision = d3pkg_data[:revision]

  # process the d3 data
  if d3pkg_data

    # Loop through the field definitions for the pkg table and process each one
    # into it's ruby attribute.
    P_FIELDS.each do |fld_key, fld_def|

      # skip if we already have a value, e.g. basename was set above.
      next if self.send(fld_key)

      # Note - the d3pkgdata has already been 'rubyized' via the D3::Database.table_records method
      # (which was used by D3::Package.package_data)
      fld_val = d3pkg_data[fld_key]

      # if we have a setter method for this key, call it to set the attribute.
      setter = "#{fld_key}=".to_sym
      self.send(setter, fld_val) if self.respond_to?(setter, true)  # the 'true' makes respond_to? look at private methods also

    end # PFIELDS.each
  end # if d3pkg_data

  # some nil-values shouldn't be nil
  @auto_groups ||= []
  @excluded_groups ||= []

  # expiration_paths should always be an array
  @expiration_paths ||= []

  # prohibiting_processes should always be an array
  @prohibiting_processes ||= []

  # these don't come from the table def.
  @admin = args[:admin]

  # dmg or pkg?
  @package_type = @receipt.to_s.end_with?(".dmg") ? :dmg : :pkg

  # this needs to be an array
  @apple_receipt_data ||= []

  # re-enable checks
  @initializing = false

end

Instance Attribute Details

#added_byString?

Returns the login name of the admin who added this packge to d3.

Returns:

  • (String, nil)

    the login name of the admin who added this packge to d3



59
60
61
# File 'lib/d3/package/attributes.rb', line 59

def added_by
  @added_by
end

#added_dateTime

Returns when was this package was added to d3.

Returns:

  • (Time)

    when was this package was added to d3



56
57
58
# File 'lib/d3/package/attributes.rb', line 56

def added_date
  @added_date
end

#adminString (readonly) Originally defined in module Basename

Returns who’s uploading, releasing, installing, or archiving this thing?.

Returns:

  • (String)

    who’s uploading, releasing, installing, or archiving this thing?

#apple_receipt_dataArray<Hash>

Returns the apple receipt data for the items installed by this pkg. When .[m]pkgs are installed, their identifiers and metadata are recorded in the OS’s receipts database and are accessible via the pkgutil command. (e.g. pkgutil –pkg-info com.company.application). Storing it in the DB allows us to do uninstalls and other client tasks without needing to index the pkg in Jamf Pro. Each hash has these keys:

  • :apple_pkg_id => String

  • :version => String

  • :installed_kb => Integer.

Returns:

  • (Array<Hash>)

    the apple receipt data for the items installed by this pkg. When .[m]pkgs are installed, their identifiers and metadata are recorded in the OS’s receipts database and are accessible via the pkgutil command. (e.g. pkgutil –pkg-info com.company.application). Storing it in the DB allows us to do uninstalls and other client tasks without needing to index the pkg in Jamf Pro. Each hash has these keys:

    • :apple_pkg_id => String

    • :version => String

    • :installed_kb => Integer



53
54
55
# File 'lib/d3/package/attributes.rb', line 53

def apple_receipt_data
  @apple_receipt_data
end

#auto_groupsArray

Returns a list of JSS::ComputerGroup names whose members get this package installed automatically.

Returns:

  • (Array)

    a list of JSS::ComputerGroup names whose members get this package installed automatically



72
73
74
# File 'lib/d3/package/attributes.rb', line 72

def auto_groups
  @auto_groups
end

#basenameString (readonly) Originally defined in module Basename

Returns the basname of the thing installed.

Returns:

  • (String)

    the basname of the thing installed

#excluded_groupsArray

Returns a list of JSS::ComputerGroup names for whose members this package is not available without force.

Returns:

  • (Array)

    a list of JSS::ComputerGroup names for whose members this package is not available without force



76
77
78
# File 'lib/d3/package/attributes.rb', line 76

def excluded_groups
  @excluded_groups
end

#expirationInteger (readonly) Originally defined in module Basename

Returns the days of disuse before an expirable edition expires. 0=never.

Returns:

  • (Integer)

    the days of disuse before an expirable edition expires. 0=never

#expiration_pathsString (readonly) Originally defined in module Basename

Returns the path to the executable that needs come to the foreground to prevent expiration.

Returns:

  • (String)

    the path to the executable that needs come to the foreground to prevent expiration

#idInteger (readonly) Originally defined in module Basename

Returns the JSS id of this package.

Returns:

  • (Integer)

    the JSS id of this package

#in_d3Boolean (readonly)

Returns does this pkg exist in the d3 pkg table?.

Returns:

  • (Boolean)

    does this pkg exist in the d3 pkg table?



95
96
97
# File 'lib/d3/package/attributes.rb', line 95

def in_d3
  @in_d3
end

#package_typeSymbol (readonly) Originally defined in module Basename

Returns Is this package a .dmg or .pkg?.

Returns:

  • (Symbol)

    Is this package a .dmg or .pkg?

#post_install_script_idInteger? Also known as: post_install_id

Returns the JSS::Script id of the post-install script, if any.

Returns:

  • (Integer, nil)

    the JSS::Script id of the post-install script, if any



86
87
88
# File 'lib/d3/package/attributes.rb', line 86

def post_install_script_id
  @post_install_script_id
end

#post_remove_script_idInteger? Also known as: post_remove_id

Returns the JSS::Script id of the post-remove script, if any.

Returns:

  • (Integer, nil)

    the JSS::Script id of the post-remove script, if any



92
93
94
# File 'lib/d3/package/attributes.rb', line 92

def post_remove_script_id
  @post_remove_script_id
end

#pre_install_script_idInteger? Also known as: pre_install_id

Returns the JSS::Script id of the pre-install script, if any.

Returns:

  • (Integer, nil)

    the JSS::Script id of the pre-install script, if any



83
84
85
# File 'lib/d3/package/attributes.rb', line 83

def pre_install_script_id
  @pre_install_script_id
end

#pre_remove_script_idInteger? Also known as: pre_remove_id

Returns the JSS::Script id of the pre-remove script, if any.

Returns:

  • (Integer, nil)

    the JSS::Script id of the pre-remove script, if any



89
90
91
# File 'lib/d3/package/attributes.rb', line 89

def pre_remove_script_id
  @pre_remove_script_id
end

#prohibiting_processesArray<String> (readonly) Originally defined in module Basename

Returns an array of Strings for matching to the output lines of ‘/bin/ps -A -c -o comm’. If there’s a match, this pkg won’t be installed or uninstalled without a graceful quit.

Returns:

  • (Array<String>)

    an array of Strings for matching to the output lines of ‘/bin/ps -A -c -o comm’. If there’s a match, this pkg won’t be installed or uninstalled without a graceful quit

#release_dateTime?

Returns when was this package made live in d3.

Returns:

  • (Time, nil)

    when was this package made live in d3



62
63
64
# File 'lib/d3/package/attributes.rb', line 62

def release_date
  @release_date
end

#released_byString?

Returns the login name of the admin who made it live.

Returns:

  • (String, nil)

    the login name of the admin who made it live



65
66
67
# File 'lib/d3/package/attributes.rb', line 65

def released_by
  @released_by
end

#remove_firstBoolean Also known as: remove_first?

Returns should any currently installed versions of this basename be uninstalled (if possible) before installing this package?.

Returns:

  • (Boolean)

    should any currently installed versions of this basename be uninstalled (if possible) before installing this package?



80
81
82
# File 'lib/d3/package/attributes.rb', line 80

def remove_first
  @remove_first
end

#revisionInteger (readonly) Originally defined in module Basename

Returns the d3 release number of the thing installed.

Returns:

  • (Integer)

    the d3 release number of the thing installed

#standardBoolean (readonly) Also known as: standard?

Returns does this pkg get installed automatically on all non-exluded clients?.

Returns:

  • (Boolean)

    does this pkg get installed automatically on all non-exluded clients?



68
69
70
# File 'lib/d3/package/attributes.rb', line 68

def standard
  @standard
end

#statusSymbol (readonly)

Returns the current status of the pkg in d3.

Returns:

  • (Symbol)

    the current status of the pkg in d3



98
99
100
# File 'lib/d3/package/attributes.rb', line 98

def status
  @status
end

#statusSymbol Originally defined in module Basename

Returns whats the d3 status of this package? One of the values of D3::Basename::STATUSES.

Returns:

  • (Symbol)

    whats the d3 status of this package? One of the values of D3::Basename::STATUSES

#versionString (readonly) Originally defined in module Basename

Returns the version of the thing installed.

Returns:

  • (String)

    the version of the thing installed

Class Method Details

.all_basenames(refresh = false) ⇒ Array<String>

An Array of basenames known to d3

Parameters:

  • refresh (Boolean) (defaults to: false)

    should the data be re-read from the database?

Returns:

  • (Array<String>)

    the basenames known to d3



184
185
186
# File 'lib/d3/package/class_methods.rb', line 184

def self.all_basenames(refresh = false)
  self.package_data(refresh).values.map{|p| p[:basename] }.uniq
end

.all_editions(refresh = false) ⇒ Array<String>

An Array of editions known to d3

Parameters:

  • refresh (Boolean) (defaults to: false)

    should the data be re-read from the database?

Returns:

  • (Array<String>)

    the basenames known to d3



194
195
196
# File 'lib/d3/package/class_methods.rb', line 194

def self.all_editions(refresh = false)
  self.package_data(refresh).values.map{|p| p[:edition] }
end

.all_filenames(refresh = false) ⇒ Hash{Integer: String}

A Hash of all packages filenames keyed by pkg id These are looked up via a DB query because otherwise we’d have to instantiate a Package object for every package which is way too slow.

Parameters:

  • refresh (Boolean) (defaults to: false)

    should the data be re-read from the database?

Returns:

  • (Hash{Integer: String})

    the pkg filenames



207
208
209
210
211
212
213
214
215
# File 'lib/d3/package/class_methods.rb', line 207

def self.all_filenames(refresh = false)
  @@filenames = nil if refresh
  return @@filenames if @@filenames
  @@filenames = {}
  qr = JSS::DB_CNX.db.query "SELECT package_id, file_name FROM packages WHERE package_id IN (SELECT package_id FROM #{D3::Package::P_TABLE[:table_name]})"
  qr.each_hash{|p| @@filenames[p["package_id"].to_i] =  p["file_name"]}
  qr.free
  @@filenames
end

.all_ids(refresh = false, api: JSS.api) ⇒ Array<Integer>

An Array of all packages ids known to d3

Parameters:

  • refresh (Boolean) (defaults to: false)

    should the data be re-read from the database?

Returns:

  • (Array<Integer>)

    the pkg ids known to d3



164
165
166
# File 'lib/d3/package/class_methods.rb', line 164

def self.all_ids(refresh = false, api: JSS.api)
  self.package_data(refresh).keys
end

.all_names(refresh = false, api: JSS.api) ⇒ Array<String>

An Array of all packages names known to d3

Parameters:

  • refresh (Boolean) (defaults to: false)

    should the data be re-read from the database?

Returns:

  • (Array<String>)

    the pkg names known to d3



174
175
176
# File 'lib/d3/package/class_methods.rb', line 174

def self.all_names(refresh = false, api: JSS.api)
  self.package_data(refresh).values.map{|p| p[:name]}
end

.auto_install_ids_for_group(group, refresh = false) ⇒ Array<Integer>

An array of ids for all pkgs that are auto-installed for a given computer group. Returns an empty array if no such group.

Parameters:

  • group (String)

    the name of the JSS group for which to find ids

  • refresh (Boolean) (defaults to: false)

    should the data be re-read from the db?

Returns:

  • (Array<Integer>)

    the ids auto-installed for that group



488
489
490
491
# File 'lib/d3/package/class_methods.rb', line 488

def self.auto_install_ids_for_group (group, refresh = false)
  pkgs = D3::Package.package_data(refresh).values
  pkgs.select{|p| p[:auto_groups].include? group}.map{|p| p[:id]}
end

.basenames_to_live_ids(refresh = false) ⇒ Hash{String => Integer}

A Hash mapping all basenames to their currently live jss id

Parameters:

  • refresh (Boolean) (defaults to: false)

    should the data be re-read from the database?

Returns:

  • (Hash{String => Integer})

    The basenames and id’s of all live packages



235
236
237
238
239
240
# File 'lib/d3/package/class_methods.rb', line 235

def self.basenames_to_live_ids(refresh = false)
  # Hashes don't have #map, so #merge back onto ourselves to have the
  # same effect.
  lp = self.live_data(refresh)
  lp.merge(lp){|id,p| p[:basename] }.invert
end

.check_computer_groups(groups) ⇒ Array<String>

Check for existence of one or more computer groups in the JSS, raise an exception if any group doesn’t exist.

Parameters:

  • groups (String, Array<String>)

    the group name(s) to check, if string, comma-separated.

Returns:

  • (Array<String>)

    valid, existing group names.



584
585
586
587
588
589
590
# File 'lib/d3/package/class_methods.rb', line 584

def self.check_computer_groups(groups)
  parsed_groups = JSS.to_s_and_a(groups)
  parsed_groups[:arrayform].each do |g|
    raise JSS::NoSuchItemError, "No ComputerGroup named '#{g}' in the JSS" unless JSS::ComputerGroup.all_names.include? g
  end
  return parsed_groups[:arrayform]
end

.deprecated_data(refresh = false) ⇒ Array<Hash>

A Hash of Package Data for all “deprecated” packages

This is the package_data Hash, limited to those packages whose status is :depreicated. These packages were once live, and still exist in the JSS and can be made live again, though that isn’t recommended.

Parameters:

  • refresh (Boolean) (defaults to: false)

    should the data be re-read from the database?

Returns:

  • (Array<Hash>)

    the deprecated packages known to d3



297
298
299
# File 'lib/d3/package/class_methods.rb', line 297

def self.deprecated_data(refresh = false)
  self.package_data(refresh, :deprecated)
end

.exclude_ids_for_group(group, refresh = false) ⇒ Array<Integer>

An array of ids for all pkgs that are exclude for a given computer group. Returns an empty array if no such group.

Parameters:

  • group (String)

    the name of the JSS group for which to find ids

  • refresh (Boolean) (defaults to: false)

    should the data be re-read from the db?

Returns:

  • (Array<Integer>)

    the ids excluded for that group



503
504
505
506
# File 'lib/d3/package/class_methods.rb', line 503

def self.exclude_ids_for_group (group, refresh = false)
  pkgs = D3::Package.package_data(refresh).values
  pkgs.select{|p| p[:excluded_groups].include? group}.map{|p| p[:id]}
end

.find_package(search_term, type = :pkg) ⇒ D3::Package?

Get a single D3::Package by using a search term.

The term is searched in this order: edition, basename, id, display name, filename.

If basename, returns the currently live pkg

The first match is returned, nil if no match

Parameters:

  • search_term (String)

    the thing to look for

  • type (Symbol) (defaults to: :pkg)

    either :pkg to return a D3::Package or :hash to return the raw D3::Package.package_data hash for the matching package. This is quicker and doesn’t instantiate a Package Object from the API.

Returns:



349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
# File 'lib/d3/package/class_methods.rb', line 349

def self.find_package(search_term, type = :pkg )
  return nil if search_term.nil?

  if self.all_editions.include? search_term
    id = D3::Package.ids_to_editions.invert[search_term]

  elsif self.all_basenames.include? search_term
    id = self.basenames_to_live_ids[search_term]

  elsif self.all_ids.include? search_term.to_i
    id = search_term.to_i

  elsif self.all_names.include? search_term
    id = D3::Package.map_all_ids_to(:name).invert[search_term]

  elsif self.all_filenames.values.include? search_term
    id = self.all_filenames.invert[search_term]

  else
    return nil
  end # if elsif.....

  return nil unless id and id.is_a? Fixnum

  return type == :pkg ? D3::Package.fetch(:id => id) : self.package_data[id]
end

.ids_for_basename(basename, refresh = false) ⇒ Array

An Array of ids for all pkgs with a given basename

Parameters:

  • basename (String)

    the basename to look for

  • refresh (Boolean) (defaults to: false)

    should the data be re-read from the database?

Returns:

  • (Array)

    The package ids with the desired basename



154
155
156
# File 'lib/d3/package/class_methods.rb', line 154

def self.ids_for_basename(basename, refresh = false)
  self.package_data(refresh).values.select{|p| p[:basename] == basename}.map{|p| p[:id]}
end

.ids_to_editions(refresh = false) ⇒ Hash{Integer => String}

A Hash mapping package ids to editions in d3

A package’s ‘edition’ is the combination of its basename, version, and revision, joined with hyphens into a String, which must be unique in d3.

Parameters:

  • refresh (Boolean) (defaults to: false)

    should the data be re-read from the database?

Returns:

  • (Hash{Integer => String})

    the current editions in d3



141
142
143
144
# File 'lib/d3/package/class_methods.rb', line 141

def self.ids_to_editions(refresh = false )
  pd = self.package_data(refresh)
  pd.merge(pd){|id,p| p[:edition]}
end

.import(ident, args) ⇒ D3::Package

Import an existing JSS::Package into d3.

A d3 basename and version must be provided.

If no revision is provided, it is set to 1.

If the JSS package is an Apple installer pkg, the read-only password for the current distribution point must be provided so that the Apple package identifier(s) can be queried from the pkg on the server.

After the D3::Package is instantiated, these and other d3-specific values can be changed before creating it on the server.

IMPORTANT: Even though the JSS package already exists, you must call #create after instantiating this new D3::Package in order to save it into d3.

Parameters:

  • ident (String, Integer)

    the name or id of the JSS::Package to import.

  • args (Hash)

    The d3-specific values for this package. Here are some of the important ones for importing. See #initialize for more details.

Options Hash (args):

  • :basename (String)

    The d3 basename to which this package will belong

  • :version (String)

    The version of the thing installed.

  • :revision (Integer)

    The revision of this pkg version in d3. Defaults to 1.

  • :dist_pw (String)

    The read-only or read-write password for the distribution point for this machine.

  • :unmount (Boolean)

    Should the dist.point be unmounted after this? Defaults to true

Returns:

  • (D3::Package)

    The newly imported d3 package object, not yet saved as a d3 pkg on the server

Raises:

  • (JSS::NoSuchItemError)


547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
# File 'lib/d3/package/class_methods.rb', line 547

def self.import (ident, args)
  id = if JSS::Package.all_ids.include? ident
    ident
  else
    JSS::Package.map_all_ids_to(:name).invert[ident]
  end
  raise JSS::NoSuchItemError, "No JSS Package with name or id matching '#{ident}'" unless id
  raise JSS::AlreadyExistsError, "That JSS package already exists in d3" if self.all_ids.include? id

  raise JSS::MissingDataError, "Importing packages requires :basename" unless args[:basename]
  raise JSS::MissingDataError, "Importing packages requires :version" if args[:version].to_s.strip.empty?

  args[:revision] ||= 1

  jss_pkg = JSS::Package.fetch :id => id

  tmp_edition = "#{args[:basename]}-#{args[:version]}-#{args[:revision]}"
  if self.all_editions.include? tmp_edition
    raise JSS::InvalidDataError, "A d3 pkg for edition #{tmp_edition} already exists."
  end # unless

  args[:id] = id
  args[:import] = true
  args[:unmount] = true if args[:unmount].nil?

  imported_pkg = self.new(args)
  imported_pkg.update_apple_receipt_data args[:dist_pw],  args[:unmount]
  imported_pkg
end

.live_basenames(refresh = false) ⇒ Array

An Array of basenames that have live packages.

Parameters:

  • refresh (Boolean) (defaults to: false)

    should the data be re-read from the database?

Returns:

  • (Array)

    the live basenames



268
269
270
# File 'lib/d3/package/class_methods.rb', line 268

def self.live_basenames(refresh = false)
  self.basenames_to_live_ids(refresh).keys
end

.live_data(refresh = false) ⇒ Hash{Integer=>Hash}

A Hash of Package Data for all live packages

This is the package_data Hash, limited to those packages whose status is :live, i.e. the package that gets installed for its basename.

Returns:

  • (Hash{Integer=>Hash})

    The live pacakge data



225
226
227
# File 'lib/d3/package/class_methods.rb', line 225

def self.live_data(refresh = false)
  self.package_data(refresh, :live)
end

.live_ids(refresh = false) ⇒ Array<String>

An Array of all packages ids that are live

Parameters:

  • refresh (Boolean) (defaults to: false)

    should the data be re-read from the database?

Returns:

  • (Array<String>)

    the live pkg ids



248
249
250
# File 'lib/d3/package/class_methods.rb', line 248

def self.live_ids(refresh = false)
  self.live_data(refresh).keys
end

.live_names(refresh = false) ⇒ Array<String>

An Array of all packages names that are live

Parameters:

  • refresh (Boolean) (defaults to: false)

    should the data be re-read from the database?

Returns:

  • (Array<String>)

    the pkg names known to d3



258
259
260
# File 'lib/d3/package/class_methods.rb', line 258

def self.live_names(refresh = false)
  self.live_data(refresh).values.map{|p| p[:name]}
end

.missing_data(refresh = false) ⇒ Array<Hash>

A Hash of Package Data for all “missing” packages

This is the package_data Hash, limited to those packages whose status us :missing - i.e. the package id no longer exists in the JSS.

Parameters:

  • refresh (Boolean) (defaults to: false)

    should the data be re-read from the database?

Returns:

  • (Array<Hash>)

    the missing packages known to d3



325
326
327
# File 'lib/d3/package/class_methods.rb', line 325

def self.missing_data(refresh = false)
  self.package_data(refresh, :missing)
end

.most_recent_package_for(basename) ⇒ D3::Package?

Get the most recent package on the server for a given basename

Parameters:

  • basename (String)

    the basename to look for

Returns:

  • (D3::Package, nil)

    the most recent package, or nil if none



383
384
385
386
387
388
389
390
391
392
393
394
395
396
# File 'lib/d3/package/class_methods.rb', line 383

def self.most_recent_package_for(basename)
  return nil unless D3::Package.all_basenames.include? basename
  # deal with potentially missing pkgs:
  # start with the highest id until we find an existing one
  self.ids_for_basename(basename).sort.reverse.each do |id|
    begin
      pkg = D3::Package.fetch :id => id
      return pkg
    rescue JSS::NoSuchItemError
      next
    end
  end
  return nil
end

.package_data(refresh = false, status = :all) ⇒ Hash<Hash>

Raw(ish) SQL data for all d3 packages as a Hash of Hashes.

The keys are the JSS ids of the packages

The values are records from the d3_packages table, as Hashes with keys matching the keys of D3::Database::PACKAGE_TABLE plus these fields from the JSS’s packages table:

:name, :require_reboot, :oses, :required_processor

This raw data, queried directly via SQL, lets us process lists of packages without instantiating each one as a D3::Package object, which is slow due to API calls for each package. We can use this data to instantiate those package objects when they are actually needed.

Parameters:

  • refresh (Boolean) (defaults to: false)

    should the data be re-read from the database?

  • status (Symbol) (defaults to: :all)

    return only data for packages with this status. See Basename::STATUSES, defaults to :all

Returns:

  • (Hash<Hash>)

    database data about all packages known to d3



58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/d3/package/class_methods.rb', line 58

def self.package_data(refresh = false, status = :all)
  @@package_data = nil if refresh

  if @@package_data.nil?

    # get a few fields from the JSS package data
    # store them in a hash keyed by id
    #jss_q = "SELECT package_id, package_name, os_requirements, require_reboot, required_processor  FROM #{JSS::Package::DB_TABLE} WHERE package_id IN (SELECT package_id from #{P_TABLE[:table_name]})"
    jss_q = "SELECT package_id, package_name, os_requirements, require_reboot, required_processor, allow_uninstall FROM #{JSS::Package::DB_TABLE}"

    r = JSS::DB_CNX.db.query jss_q
    jss_data = {}
    r.each_hash{|jpkg| jss_data[jpkg["package_id"].to_i] = jpkg }

    r.free

    # get the table records and add in the appropriate names
    @@package_data = {}

    D3::Database.table_records(D3::Package::P_TABLE).each do |p|
      d3_id = p[:id]

      @@package_data[d3_id] = p

      @@package_data[d3_id][:edition] = "#{p[:basename]}-#{p[:version]}-#{p[:revision]}"

      # D3::Database.table_records returns NULL as nil even if converting with STRING_TO_INT
      # which in general is a good thing, but expiration should always be an integer and NULL should be 0
      @@package_data[d3_id][:expiration] = p[:expiration].to_i

      jinfo = jss_data[d3_id]

      # is this pkg still in the JSS?
      if jinfo
        @@package_data[d3_id][:name] = jinfo["package_name"]
        @@package_data[d3_id][:oses] = jinfo["os_requirements"]
        @@package_data[d3_id][:reboot] = (jinfo["require_reboot"] == "1") # boolean
        @@package_data[d3_id][:required_processor] = jinfo["required_processor"]
        @@package_data[d3_id][:removable] = (jinfo["allow_uninstall"] == "1")
      # or is it missing?
      else
        @@package_data[d3_id][:status] = :missing
        @@package_data[d3_id][:name] = "** missing from jss **"
        @@package_data[d3_id][:oses] = ""
        @@package_data[d3_id][:reboot] = false
        @@package_data[d3_id][:required_processor] = "None"
        @@package_data[d3_id][:removable] = false
      end #  if jinfo
    end #  D3::Database.table_records(D3::Package::P_TABLE).each do |p|
  end # if @@package_data.nil?

  if status == :all
    return @@package_data
  else
    raise JSS::InvalidDataError, "status must be one of :#{STATUSES.join(', :')}" unless STATUSES.include? status
    # reject because Hash#select returns an array of arrays
    return @@package_data.reject{|id,p|  p[:status] != status }
  end #if status == :all
end

.packages_for_script(script, script_type = nil, refresh = false) ⇒ Array<Integer>

An Array of pkg ids for all pkgs that use a given script, optionally limiting to those pkgs that use the script for a given purpose.

Parameters:

  • script (Integer, String)

    The name or ID number of the script

  • script_type (Symbol, nil) (defaults to: nil)

    The script-type by which to limit the results. One of SCRIPT_TYPES, or nil for all types

  • refresh (Boolean) (defaults to: false)

    Should the data be re-read from the server?

Returns:

  • (Array<Integer>)

    the ids for each package that uses the script



452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
# File 'lib/d3/package/class_methods.rb', line 452

def self.packages_for_script (script, script_type = nil, refresh = false)

  if script_type
    raise JSS::InvalidDataError, "Script type must be one of :#{SCRIPT_TYPES.join(' :')}" unless SCRIPT_TYPES.include? script_type
  end # if screipt type

  pkgs = []

  # confirm the ID of the script..
  sid = JSS::Script.all_ids(refresh).include?(script) ? script : nil
  sid ||= JSS::Script.map_all_ids_to(:name).invert[script]

  # script id has to exist in JSS and some d3 pkg
  return pkgs unless sid and self.scripts(refresh)[sid]

  self.scripts(refresh)[sid].each do |pkg_id, uses|
    if script_type
      pkgs << pkg_id if uses.include? script_type
    else
      pkgs << pkg_id
    end
  end # each script id, pkgs

  pkgs
end

.pilot_data(refresh = false) ⇒ Hash<Hash>

A Hash of Package Data for all “pilot” packages

This is the package_data Hash, limited to those packages whose status is :pilot, i.e. they are not live and are newer than the live pkg for their basename.

Parameters:

  • refresh (Boolean) (defaults to: false)

    should the data be re-read from the database?

Returns:

  • (Hash<Hash>)

    the pilotable packages known to d3



282
283
284
# File 'lib/d3/package/class_methods.rb', line 282

def self.pilot_data(refresh = false)
  self.package_data(refresh, :pilot)
end

.receipt_data_from_bundle_pkg(pkg_path) ⇒ Object

givin a Pathname to a bundle pkg, return an array of hashes with data for all the pkg rcpts hat will be installed each has includes at least :apple_pkg_id, :version, and :installed_kb

Thanks to Greg Neagle for inspiration on this method from munkicommon.py



675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
# File 'lib/d3/package/class_methods.rb', line 675

def self.receipt_data_from_bundle_pkg (pkg_path)
  pkg_path = Pathname.new(pkg_path) unless pkg_path.is_a? Pathname
  rcpts = []

  # if this is a single pkg, not a metapkg, data comes from Info.plist
  if pkg_path.to_s.end_with? ".pkg"
    rcpt_data = {}
    info_plist = pkg_path + "Contents/Info.plist"
    if info_plist.exist?
      plist = D3.parse_plist info_plist
      rcpt_data[:apple_pkg_id] = plist["CFBundleIdentifier"]
      rcpt_data[:apple_pkg_id] ||= plist["Bundle Identifier"]
      rcpt_data[:apple_pkg_id] ||= pkg_path.basename.to_s

      rcpt_data[:installed_kb] = plist["IFPkgFlagInstalledSize"]

      rcpt_data[:version] = plist["CFBundleShortVersionString"]
      rcpt_data[:version] ||= plist["CFBundleVersion"]
      rcpt_data[:version] ||= plist["Bundle versions string, short"]

      rcpts << rcpt_data
    end # if plist exist?

  end

  # if rcpts is empty, it could be an mpkg, which installs more than one pkg
  if rcpts.empty?
    contents_dir = info_plist = pkg_path + "Contents"
    dist_file = contents_dir.children.select{|c| c.to_s.end_with? ".dist"}[0]
    return self.receipt_data_from_xml(dist_file, pkg_path) if dist_file

    # no dist file - any other embedded packages?
    pkg_path.find do |sub_item|
      next unless sub_item.to_s =~ PKG_RE
      rcpts += self.receipt_data_from_bundle_pkg(sub_item)
    end # pkg_path.find
  end # if pkg_path.to_s.end_with? ".pkg"

  rcpts

end

.receipt_data_from_flat_pkg(pkg_path) ⇒ Object

Givinn a Pathname to a flat package, return an array of hashes with data for all the pkg rcpts hat will be installed each has includes at least :apple_pkg_id, :version, and :installed_kb

Thanks to Greg Neagle for inspiration on this method from munkicommon.py



620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
# File 'lib/d3/package/class_methods.rb', line 620

def self.receipt_data_from_flat_pkg (pkg_path)

  pkg_path = Pathname.new(pkg_path)
  rcpts = []

  pkg_contents = `/usr/bin/xar -tf #{Shellwords.escape pkg_path.to_s}`
  raise "Could not look at contents of flat package #{pkg_path.basename}" unless $CHILD_STATUS.exitstatus == 0

  start_dir = Pathname.pwd
  work_dir = Pathname.new Dir.mktmpdir
  Dir.chdir work_dir

  begin
    # loop thru the items in the flat pkg
    # extracting any PackageInfo and Distribution files
    xml_files = []

    pkg_contents.each_line do |line|
      line.chomp!

      # if there's a top level Dist or PackageInfo, use it exclusively
      if line. == "PackageInfo" or line == "Distribution"
        xml_files = [line]
        break

      # otherwise find all sub PackageInfos
      elsif line.end_with? ".pkg/PackageInfo"
        xml_files << line
      end

    end # pkg_contents.each_line do line

    # Extract whatever files we found interesting
    xml_files.each do |xml_file|
      system "/usr/bin/xar", "-xf", pkg_path.to_s, xml_file
      raise raise "Error reading #{xml_file} from flat package #{pkg_path.basename}" unless $CHILD_STATUS.exitstatus == 0
      extracted_file = work_dir + xml_file
      rcpts += self.receipt_data_from_xml(extracted_file, pkg_path)
    end # xml files.each

  ensure
    Dir.chdir start_dir
    work_dir.rmtree
  end # begin

  rcpts

end

.receipt_data_from_pkg(pkg_path) ⇒ Array<Hash>

Givin a Pathname to a package, return an array of hashes with data for all the pkg rcpts that will be installed each hash includes at least :apple_pkg_id, :version, and :installed_kb

Thanks to Greg Neagle for inspiration on this method from munkicommon.py

Parameters:

  • pkg_path (String, Pathname)

    the path to a .pkg to scan

Returns:

  • (Array<Hash>)

    the Apple receipt data for the pkg



602
603
604
605
606
607
608
609
610
611
612
# File 'lib/d3/package/class_methods.rb', line 602

def self.receipt_data_from_pkg (pkg_path)
  pkg_path = Pathname.new(pkg_path) unless pkg_path.is_a? Pathname
  raise "The path given must end with .pkg or .mpkg" unless pkg_path.to_s =~ PKG_RE
  raise "Package '#{pkg_path}' doesn't exist" unless pkg_path.exist?

  if pkg_path.directory?
    self.receipt_data_from_bundle_pkg(pkg_path).uniq
  else
    self.receipt_data_from_flat_pkg(pkg_path).uniq
  end
end

.receipt_data_from_xml(xml_file_path, pkg_path = nil) ⇒ Object

Parse an xml file (like a .dist, Distribution, or PackageInfo file) and find all pkg-ref or pkg-info elements to extract their pkg ids and other data, or locate and recurse on any sub-pkgs. Return an array of hashes of pkg data. xml_file_path is a String or Pathname to the xml file. If the xml file is not embedded in a pkg (eg it was extracted from a flat pkg), provide the path to the pkg as the second arg.

Thanks to Greg Neagle for inspiration on this method from munkicommon.py



725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
# File 'lib/d3/package/class_methods.rb', line 725

def self.receipt_data_from_xml(xml_file_path, pkg_path = nil)

  # both args must be Pathnames if they're strings
  xml_file_path = Pathname.new(xml_file_path) unless xml_file_path.is_a? Pathname
  pkg_path = Pathname.new(pkg_path) if pkg_path and !pkg_path.is_a?(Pathname)

  # this will be returned - an array of hashes of data about this pkg and sub-pkgs
  rcpts = []

  # parse the xml
  doc = REXML::Document.new(File.new(xml_file_path))

  ####
  # pkg-info elements
  doc.elements.each("//pkg-info") do |pkg_info_element|

    attribs = pkg_info_element.attributes

    # we only care about elements with both an identifier and a version
    next unless attribs["identifier"] && attribs["version"]

    data = { :apple_pkg_id => attribs["identifier"], :version => attribs["version"] }

    payload = pkg_info_element.elements.to_a('payload')[0]
    data[:installed_kb] = payload.attributes["installKBytes"].to_i if payload.attributes["installKBytes"]

    rcpts << data unless rcpts.include? data
    return rcpts unless rcpts.empty?
  end # doc.elements.each("*/pkg-ref") do |pkg|

  ####
  # pkg-ref elements
  all_ref_data = {}
  doc.elements.each("//pkg-ref") do |pkg_ref_element|

    attribs = pkg_ref_element.attributes

    next unless attribs["id"] && attribs["version"]

    # make a new hash for this pkg if needed
    this_ref_data = {:apple_pkg_id => attribs["id"]}

    # any inner-content of the element is a path to a sub-pkg
    if pkg_ref_element.text
      this_ref_data[:sub_pkg_ref] = pkg_ref_element.text

      # if its a file: url, its relative to the pkg_path
      if this_ref_data[:sub_pkg_ref] =~ /^file:.*\.pkg$/
        this_ref_data[:sub_pkg_path] = (pkg_path || xml_file_path.dirname) + URI.decode(this_ref_data[:sub_pkg_ref][5..-1])

      # but it might be a relative path from the cwd, starting with a #, which suould be ignored.
      elsif this_ref_data[:sub_pkg_ref] =~ /^#.*\.pkg$/
        this_ref_data[:sub_pkg_path] = xml_file_path.dirname + URI.decode(this_ref_data[:sub_pkg_ref][1..-1])
      end # if this_ref_data[:sub_pkg_ref] =~ /^file:.*\.pkg$/

    end # pkg_ref_element.text

    this_ref_data[:version] = attribs["version"]
    this_ref_data[:installed_kb] = attribs["installKBytes"].to_i
    this_ref_data[:auth] = attribs["auth"]

    sub_pkg_data = []

    # if we have a file path to an existing pkg, try to get data from it rather than from this xml
    if this_ref_data[:sub_pkg_path] and this_ref_data[:sub_pkg_path].exist?
      sub_pkg_data = receipt_data_from_pkg(this_ref_data[:sub_pkg_path])
    end

    # did the sub pkg have data?
    # if not, use the data from this xml, as long as we have a version
    #
    if sub_pkg_data.empty?
      if this_ref_data[:version]
        this_ref_data.delete :sub_pkg_path
        rcpts << this_ref_data
      end

    else
      # but if it did, then use the data from the sub pkg.
      rcpts += sub_pkg_data

    end # this_ref_data[:sub_pkg_path]

  end # doc.elements.each("*/pkg-ref") do |pkg_ref_element|

  # clean up each rcpt hash, subpkg ref isn't needed any more
  rcpts.each{|r| r.delete  :sub_pkg_ref }

  rcpts

end

.scripts(refresh = false) ⇒ Hash{Integer => Hash{Integer => Array<Symbol>}}

A Hash of Hashes of all scripts used by d3 packages. Each key is a script ID, Each value is a sub-Hash with one entry per d3 pkg that uses the script.

The sub-Hashes have pkg id’s as keys, and an Array of script usages as values. (since a pkg can use the same script for any or all of the 4 script types)

Example: {

123 => { 234 => [:pre_install] },

456 => { 234 => [:post_install],
         345 => [:post_install, :pre_remove] }

}

In the example above,

- script id 123 is used by pkg id 234 as a pre-install script
- script id 456 is used by pkg id 234 as a post-install script
  and used by pkg id 345 as both a post-install and pre-remove script.

Parameters:

  • refresh (Boolean) (defaults to: false)

    should the data be re-read from the database?

Returns:

  • (Hash{Integer => Hash{Integer => Array<Symbol>}})

    the scripts used by packages in d3



423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
# File 'lib/d3/package/class_methods.rb', line 423

def self.scripts(refresh = false)
  scripts = {}
  self.package_data(refresh, :all).each do |pkg_id, pkg_data|
    SCRIPT_TYPES.each do |script_type|
      script_id_key = "#{script_type}_script_id".to_sym
      if pkg_data[script_id_key]
        scr_id = pkg_data[script_id_key]
        scripts[scr_id] ||= {}
        scripts[scr_id][pkg_id] ||= []
        scripts[scr_id][pkg_id] << script_type
      end
    end # each script type
  end # do each pkg_id, pkg_data
  scripts
end

.skipped_data(refresh = false) ⇒ Array<Hash>

A Hash of Package Data for all “skipped” packages

This is the package_data Hash, limited to those packages whose status us :skipped - i.e. the were never made live before a newer pkg of the same basename was made live.

Parameters:

  • refresh (Boolean) (defaults to: false)

    should the data be re-read from the database?

Returns:

  • (Array<Hash>)

    the deprecated packages known to d3



311
312
313
# File 'lib/d3/package/class_methods.rb', line 311

def self.skipped_data(refresh = false)
  self.package_data(refresh, :skipped)
end

.statuses_by(identifier, refresh = false) ⇒ Hash{String,Integer => Symbol] the statuses of the packages

A Hash of package identifiers (id’s, names, editions) as keys, to the status of the package (symbols)

Parameters:

  • identifier (Symbol)

    one of :id, :name, or :edition

Returns:

  • (Hash{String,Integer => Symbol] the statuses of the packages)

    Hash{String,Integer => Symbol] the statuses of the packages

Raises:

  • (JSS::InvalidDataError)


125
126
127
128
129
130
# File 'lib/d3/package/class_methods.rb', line 125

def self.statuses_by (identifier, refresh = false)
  raise JSS::InvalidDataError, "identifier must be one of :id, :edition, or :name" unless [:id, :name, :edition].include? identifier
  stati = {}
  self.package_data(refresh).values.each{|pkg| stati[pkg[identifier]] = pkg[:status] }
  stati
end

Instance Method Details

#<=>(other) ⇒ Object Originally defined in module Basename

Use comparable to give sortability and equality.

#add_auto_groups(groupnames) ⇒ void

This method returns an undefined value.

Add one or more groups the to list of auto_groups. The arg is a comma-separated string or an array of group names.

Parameters:

  • groupnames (String, Array)

    the names of the groups to add



207
208
209
210
211
212
213
214
215
# File 'lib/d3/package/setters.rb', line 207

def add_auto_groups (groupnames)
  # are they real groups?
  new_groups = validate_groups(groupnames)
  # if they're already there, just return
  return @auto_groups if (@auto_groups & new_groups) == new_groups
  validate_non_overlapping_groups new_groups, @excluded_groups
  @auto_groups += new_groups
  @need_to_update_d3 = true unless @initializing
end

#add_excluded_groups(groupnames) ⇒ void

This method returns an undefined value.

Add one or more groups the to list of excluded_groups. The arg is a comma-separated string or an array of group names.

Parameters:

  • groupnames (String, Array)

    the names of the groups to add



256
257
258
259
260
261
262
263
264
# File 'lib/d3/package/setters.rb', line 256

def add_excluded_groups (groupnames)
  # are they real groups?
  new_groups = validate_groups(groupnames)
  # if they're already there, just return
  return @excluded_groups if (@excluded_groups &  new_groups) == new_groups
  validate_non_overlapping_groups new_groups, @auto_groups
  @excluded_groups += new_groups
  @need_to_update_d3 = true unless @initializing
end

#add_expiration_path(path) ⇒ Object

Add a path to expiration_paths The paths are those recorded in d3RepoMan’s timestamp plists.

Examples:

“/Applications/FileMaker Pro 11/FileMaker Pro.app/Contents/MacOS/Filemaker Pro”

Parameters:

  • new_val (String, Pathname)


145
146
147
148
149
# File 'lib/d3/package/setters.rb', line 145

def add_expiration_path (path)
  path = validate_expiration_path(path)
  @expiration_paths << path
  @need_to_update_d3 = true unless @initializing
end

#admin=(new_val = @admin) ⇒ void

This method returns an undefined value.

Set the login name of the admin who’s doing something with this pkg

Parameters:

  • new_val (String) (defaults to: @admin)

    the name of the admin



38
39
40
41
42
# File 'lib/d3/package/setters.rb', line 38

def admin= (new_val = @admin)
  return nil if new_val == @admin
  raise "admin can't be empty!" if new_val.to_s == ''
  @admin = new_val
end

#auto_clean(admin) ⇒ void

This method returns an undefined value.

Perform any auto_cleanup, if the config says we should

Parameters:

  • admin (String)

    the admin doing the make-live



321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
# File 'lib/d3/package/server_actions.rb', line 321

def auto_clean  (admin)

  ### safety
  return unless D3::CONFIG.admin_auto_clean

  puts "Starting auto-clean of old packages for '#{@basename}'"

  #### First the deprecated pkgs

  # the id's of the deprecated pkgs for this basename, in numerical order
  # the last ones are the newest.
  deprecated_ids = D3::Package.deprecated_data.values.select{|dp| dp[:basename] == @basename}
  deprecated_ids.map!{|dp| dp[:id] }.sort!

  # keeping any?
  number_deprecated_to_keep = D3::CONFIG.admin_auto_clean_keep_deprecated
  number_deprecated_to_keep ||= 0

  puts "Keeping #{number_deprecated_to_keep} deprecated packages."

  # 'pop' pulls them off the end
  deprecated_ids_to_keep = []
  number_deprecated_to_keep.times{ deprecated_ids_to_keep << deprecated_ids.pop  }
  deprecated_ids_to_keep.compact!

  # delete them if we should
  deprecated_ids.each do |id|
    next if deprecated_ids_to_keep.include? id
    victim = D3::Package.fetch(:id => id)
    victim.delete(
      admin: admin,
      keep_scripts: false,
      keep_in_jss: false,
      rwpw: D3::Admin::Auth.rw_credentials(:dist)[:password]
    )
    puts "Deleted deprecated package: #{victim.edition}, id:#{victim.id}, filename: #{victim.filename}."
  end

  #### then the skipped pkgs

  skipped_ids = D3::Package.skipped_data.values.select{|sp| sp[:basename] == @basename}
  skipped_ids.map!{|sp| sp[:id] }.sort!

  # keep the ones newer than the just-deprecated pkg?
  if D3::CONFIG.admin_auto_clean_keep_latest_pilots
    deprecated_ids = D3::Package.deprecated_data(:refresh).values.select{|dp| dp[:basename] == @basename}
    deprecated_ids.map!{|dp| dp[:id] }
    just_deprecated = deprecated_ids.max
    just_deprecated ||= 0
    skipped_ids_to_keep = skipped_ids.select{|id| id > just_deprecated }
    puts "Keeping most recent pilot packages as skipped."
  # no, delete them all
  else
    skipped_ids_to_keep  = []
    puts "Not keeping any pilot or skipped packages."
  end

  # delete them if we should
  skipped_ids.each do |id|
    next if skipped_ids_to_keep.include? id
    victim = D3::Package.fetch(:id => id)
    victim.delete(
      admin: admin,
      keep_scripts: false,
      keep_in_jss: false,
      rwpw: D3::Admin::Auth.rw_credentials(:dist)[:password]
    )
    puts "Deleted skipped package: #{victim.edition}, id:#{victim.id}, filename: #{victim.filename}."
  end
  puts "Finished auto-clean of old packages for '#{@basename}'"
  return true
end

#basename=(new_val = @basename) ⇒ Object

Set the basename of this package

new_val = string



48
49
50
51
52
53
# File 'lib/d3/package/setters.rb', line 48

def basename= (new_val = @basename)
  return nil if new_val == @basename
  validate_edition "#{new_val}-#{@version}-#{@revision}"
  @basename = new_val.to_s
  @need_to_update_d3 = true unless @initializing
end

#basename_exist?(name) ⇒ Boolean Originally defined in module Validate

Check the existence of a basename in d3.

Parameters:

  • name (String)

    the basename to check

Returns:

  • (Boolean)

    does that basename exist in d3?

#bomObject



75
# File 'lib/d3/package/aliases.rb', line 75

alias bom index

#check_cpuObject

Check if this machine is OK wrt to the processor limitations Raise an exception if not

return [void]

Raises:



87
88
89
90
# File 'lib/d3/package/validate.rb', line 87

def check_cpu
  my_cpu = `/usr/bin/uname -p`.chomp
  raise D3::InstallError, "This machine doesn't have the correct OS to install #{self.edition}." unless JSS.processor_ok? @required_processor, my_cpu
end

#check_for_deprecatedObject

Check that we’re not installing a deprecated pkg, and raise an exception if we are.

return [void]

Raises:



48
49
50
# File 'lib/d3/package/validate.rb', line 48

def check_for_deprecated
  raise D3::InstallError, "#{edition} is deprecated. Use --force if needed." if deprecated?
end

#check_for_exclusionsObject

Check if this machine is in an excluded group. Raise an exception if so.

return [void]

Raises:



65
66
67
68
69
# File 'lib/d3/package/validate.rb', line 65

def check_for_exclusions
  excl_grps = D3::Client.computer_groups & @excluded_groups
  raise D3::InstallError, "This machine is excluded for #{edition}. Use --force if needed." unless excl_grps.empty?
  true
end

#check_for_newer_versionObject

Check that there’s not a newer version of this thing alreay installed Raise an exception if so.

return [void]

Raises:



38
39
40
41
42
# File 'lib/d3/package/validate.rb', line 38

def check_for_newer_version
  rcpt = D3::Client::Receipt.all[@basename] # if D3::Client::Receipt.basenames.include? @basename
  return unless rcpt
  raise D3::InstallError, "The installed #{rcpt.edition} (#{rcpt.status}) is the same or newer. Use --force if needed." if rcpt.id >= @id
end

#check_for_skippedObject

Check that we’re not trying to install a skipped pkg, and raise an exception if we are.

return [void]

Raises:



56
57
58
# File 'lib/d3/package/validate.rb', line 56

def check_for_skipped
  raise D3::InstallError, "#{edition} was skipped. Use --force if needed." if skipped?
end

#check_osesObject

Check if this machine is OK wrt to the os limitations Raise an exception if not

return [void]

Raises:



76
77
78
79
80
# File 'lib/d3/package/validate.rb', line 76

def check_oses
  my_os = `/usr/bin/sw_vers -productVersion`.chomp
  raise D3::InstallError, "This machine doesn't have the correct OS to install #{self.edition}." unless JSS.os_ok? @os_requirements, my_os
  true
end

#createInteger

Create this package in the JSS if needed, and in d3

Returns:

  • (Integer)

    the JSS id of the package

Raises:

  • (JSS::MissingDataError)


34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/d3/package/server_actions.rb', line 34

def create

  # if it's already there, just return
  return @id if @in_d3

  # gotta know who did this
  raise JSS::MissingDataError, "An admin name must be set before creating this new d3 package. Use #admin= " unless @admin

  # create the JSS package if needed
  super unless @in_jss

  # who and when are we adding this pkg?
  @added_date = Time.now
  @added_by = @admin

  # change status from unsaved to pilot
  @status = :pilot

  # loop through the field definitions, and
  # use them to get data for the insert statement
  field_names = []
  sql_values = []
  P_FIELDS.each_pair do |key,field_def|
    field_names << field_def[:field_name]
    # nils and empty strings become NULL
    sql_values << (self.send(key).to_s.empty? ? 'NULL' : "'#{to_sql(key)}'")
  end # do |key,field_def


  # use the two arrays to build the SQL statement
  stmt = JSS::DB_CNX.db.prepare <<-ENDINSERT
INSERT INTO #{P_TABLE[:table_name]} (
  #{field_names.join(",\n  ")}
) VALUES (
  #{sql_values.join(",\n  ")}
)
  ENDINSERT

  # Execute it to create the record
  stmt_result = stmt.execute


  # while we're writing to the db, mark any missing packages as missing
  mark_missing_packages


  @in_d3 = true
  return @id
end

#created?Boolean

Returns Is this pkg in on the server?.

Returns:

  • (Boolean)

    Is this pkg in on the server?



52
53
54
# File 'lib/d3/package/questions.rb', line 52

def created?
  @in_jss and @in_d3
end

#delete(keep_in_jss: false, keep_scripts: false, admin: @admin, rwpw: nil) ⇒ Array<String>

Delete this package from d3, possibly leaving it in the JSS

Parameters:

  • keep_in_jss (Boolean) (defaults to: false)

    should we keep the JSS package around? defaults to false

  • keep_scripts (Boolean) (defaults to: false)

    should the related scripts be kept in the JSS?

  • admin (String) (defaults to: @admin)

    who’s doing this?

  • rwpw (String) (defaults to: nil)

    the read-write for the master distr. point

Returns:

  • (Array<String>)

    a textual list of scripts delted and not deleted because they’re in use by other d3 pkgs or jamf policies

    (empty if keep_scripts is true)
    


408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
# File 'lib/d3/package/server_actions.rb', line 408

def delete (keep_in_jss: false, keep_scripts: false, admin: @admin, rwpw: nil)

  unless keep_in_jss
    # raise an exception if any polcies are using this pkg.
    pols = policy_ids
    unless pols.empty?
      names = pols.map{|pid| JSS::Policy.map_all_ids_to(:name)[pid]}.join(', ')
      raise JSS::UnsupportedError, "Can't delete package from JSS, used by these policies: #{names} "
    end # unless pols.empty
  end # unles keep in jss

  # use @ admin if its defined and needed
  admin ||= @admin

  # delete scripts or not?  the result is an array of what happened.
  script_actions = keep_scripts ? [] : delete_pkg_scripts

  # delete it from the pakcages table
  stmt = JSS::DB_CNX.db.prepare "DELETE FROM #{P_TABLE[:table_name]} WHERE #{P_FIELDS[:id][:field_name]} = '#{@id}'"
  stmt_result = stmt.execute

  @status = :deleted

  # delete it from the JSS unless asked not to
  unless keep_in_jss
     super delete_file: true, rw_pw: rwpw, unmount: false
  end

 # while we're writing to the db, mark any missing packages as missing
  mark_missing_packages

  # update our knowledge of the world
  D3::Package.package_data :refresh

  return script_actions
end

#deleted?Boolean Originally defined in module Basename

Is the status :deleted?

Returns:

  • (Boolean)

#deprecated?Boolean

Returns Is this pkg in pilot? (saved, but never made live).

Returns:

  • (Boolean)

    Is this pkg in pilot? (saved, but never made live)



64
65
66
# File 'lib/d3/package/questions.rb', line 64

def deprecated?
  @status == :deprecated
end

#editionString Originally defined in module Basename

While several packages can have the same basename, the combination of basename, version, and revision (called the ‘edition’) must be unique among the d3 packages.

Returns:

  • (String)

    the basename, version ,and revision of this package, joined with hyphens

#edition_exist?(edition) ⇒ Boolean Originally defined in module Validate

Check the existence of an edition in d3.

Parameters:

  • name (String)

    the edition to check

Returns:

  • (Boolean)

    does that edition exist in d3?

#expiration=(new_val = @expiration) ⇒ void

This method returns an undefined value.

Set the expiration period for all installs of this pkg. Once installed, if this many days go by without the @expiration_paths being launched, as noted by d3repoman, the pkg will be silently uninstalled.

Use nil, false, or 0 (the default) to prevent expiration.

When expiration happens, a policy can be triggered to notify the user or take other actions. See Client#expiration_policy

Can be over-ridden on a per-install basis using the :expiration option with the #install method

Parameters:

  • new_val (Integer) (defaults to: @expiration)

    The number of days with no launch before expiring.



111
112
113
114
115
116
# File 'lib/d3/package/setters.rb', line 111

def expiration= (new_val = @expiration)
  return @expiration if new_val == @expiration
  new_val ||= 0
  @expiration = validate_expiration(new_val)
  @need_to_update_d3 = true unless  @initializing
end

#expiration_paths=(new_val = @expiration_paths) ⇒ void

This method returns an undefined value.

Set the expiration paths for this pkg. These are abosulute paths to executables one of which must be brought to the foreground at least once every @expiration days to prevent silent un-installing of this package.

The paths are those recorded in d3RepoMan’s timestamp plists.

Examples:

“/Applications/FileMaker Pro 11/FileMaker Pro.app/Contents/MacOS/Filemaker Pro”

Parameters:

  • new_val (String, Pathname, Array<String,Pathname>) (defaults to: @expiration_paths)

    The expiration paths. A Pathname for a single path, a String for single, or multiple (comma-separated) or an Array of Strings or Pathnames for single paths. Each path must be an absolute path starting with a /



133
134
135
136
137
# File 'lib/d3/package/setters.rb', line 133

def expiration_paths= (new_val = @expiration_paths)
  return @expiration_paths if D3::Admin::OPTIONS[:expiration_paths][:compare].call(@expiration_paths, new_val)
  @expiration_paths = validate_expiration_paths (new_val)
  @need_to_update_d3 = true unless @initializing
end

#expiration_paths_match?(other_exp_paths) ⇒ Boolean Originally defined in module Basename

Does a given array of pathnames have the same elements as This is generally used to compare two @expiration_paths arrays for “equality”

Parameters:

  • other_exp_paths (Array)

    An array if Pathnames to compare to @expiration_paths

Returns:

  • (Boolean)

    Are they the same aside from order?

#expires?Boolean

Returns Does this pkg expire by default?.

Returns:

  • (Boolean)

    Does this pkg expire by default?



83
84
85
# File 'lib/d3/package/questions.rb', line 83

def expires?
  @expiration.to_i > 0 && @expiration_paths
end

#file_listObject



76
# File 'lib/d3/package/aliases.rb', line 76

alias file_list installed_files

#filename_exist?(name) ⇒ Boolean Originally defined in module Validate

Check the existence of a filename in the JSS.

Parameters:

  • name (String)

    the name to check

Returns:

  • (Boolean)

    does that package filename exist in d3?

#filesObject



77
# File 'lib/d3/package/aliases.rb', line 77

alias files installed_files

#formatted_detailsString

Generate a human-readable string of details about this installer

Returns:

  • (String)

    the package details in a human readble format.



84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/d3/package/getters.rb', line 84

def formatted_details
  os_disp = JSS.to_s_and_a(@os_requirements)[:stringform]
  auto_disp = JSS.to_s_and_a(@auto_groups)[:stringform]
  excl_disp = JSS.to_s_and_a(@excluded_groups)[:stringform]

  msg = <<-END_DEETS
Edition: #{edition}
Status: #{@status}
---- Description ----
#{@notes.gsub("\r", "\n") if @notes}
---------------------
Added by: #{@added_by or 'unknown'}
Added date: #{@added_date ? @added_date.strftime('%Y-%m-%d') : 'unknown'}
Jamf Pro Package: #{@name} (id: #{@id})
Filename: #{@filename}
Category: #{@category or 'None'}
Needs reboot (puppytime): #{@reboot_required or 'false'}
Un-installable: #{removable? or 'false'}
Pre-install script: #{pre_install_script_name or 'None'}
Post-install script: #{post_install_script_name or 'None'}
Pre-remove script: #{pre_remove_script_name or 'None'}
Post-remove script: #{post_remove_script_name or 'None'}
CPU limitation: #{@required_processor or 'None'}
OS limitations: #{os_disp.empty? ? 'None' : os_disp}
Uninstalls older versions: #{@remove_first or 'false'}
Installation prohibited by process(es): #{D3::Admin::OPTIONS[:prohibiting_processes][:display_conversion].call @prohibiting_processes or 'None'}
Auto installed for groups: #{auto_disp.empty? ? 'None' : auto_disp}
Excluded for groups: #{excl_disp.empty? ? 'None' : excl_disp}
Expiration period: #{@expiration.to_i} days
Expiration path(s): #{D3::Admin::OPTIONS[:expiration_paths][:display_conversion].call @expiration_paths}
Released by: #{@released_by or '-'}
Release date: #{@release_date ? @release_date.strftime('%Y-%m-%d') : '-'}
  END_DEETS
end

#index(files_only = false) ⇒ Array<String>

The index of this package This is an array of paths (as Strings) from the pkg’s Jamf Pro index

Parameters:

  • files_only (Boolean) (defaults to: false)

    ignore directories, only return files

Returns:

  • (Array<String>)


126
127
128
129
130
131
132
# File 'lib/d3/package/getters.rb', line 126

def index (files_only = false)
  q =  "SELECT file FROM #{PKG_CONTENTS_TABLE} WHERE package_id = #{@id}"
  q += " AND mode NOT LIKE 'd%'" if files_only
  @index = []
  JSS::DB_CNX.db.query(q).each {|record| @index << record[0]}
  @index
end

#indexed?Boolean

Returns Is this pkg ‘indexed’ in the jss, so that it can be removable?.

Returns:

  • (Boolean)

    Is this pkg ‘indexed’ in the jss, so that it can be removable?



89
90
91
# File 'lib/d3/package/questions.rb', line 89

def indexed?
  not index.empty?
end

#install(args = {}) ⇒ String

Install this pkg on this machine. D3 pkgs are more involved than plain JSS pkgs.

Parameters:

  • args (Hash) (defaults to: {})

    The options for installation, see also the options to JSS::Package#install, which are accepted here.

Returns:

  • (String)

    the output of the jamf install command

Raises:



89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
# File 'lib/d3/package/client_actions.rb', line 89

def install(args = {})

  # force can't get around these:
  raise D3::InstallError, "This package is missing from the JSS, cannot install." if @status == :missing
  check_oses
  check_cpu

  begin  # for the ensure below

    # if this item is now being installed by puppies at logout,
    # the PuppyQ has our admin name, force setting, and expiration.
    # It if isn't in the puppyQ, then those values should
    # have come from the args
    if args[:puppywalk] and D3::PUPPY_Q.q[@basename] then
      args[:admin] = D3::PUPPY_Q.q[@basename].admin ? D3::PUPPY_Q.q[@basename].admin : "puppy-install"
      args[:force] = D3::PUPPY_Q.q[@basename].force
      args[:expiration] = D3::PUPPY_Q.q[@basename].expiration
    else
      # if this is a manual install, we should know
      # who's doing it
      args[:admin] ||= D3.admin
    end

    forced = args[:force] or D3::forced?

    # excluded pkgs
    check_for_exclusions  unless forced

    @admin = args[:admin]

    # pilot?
    D3::Client.set_env :installing, edition
    D3::Client.set_env :pkg_status, @status

    # force?
    @using_force = forced ? " with force" : ""

    # pilot installs need an admin from the args or the queue
    raise JSS::MissingDataError, "Missing :admin for pilot install." if pilot? and @admin == D3::AUTO_INSTALL_ADMIN

    # If we aren't actually installing the puppy queue items
    # and there's already a member of this basename in the queue
    # raise an exception,
    if D3::PUPPY_Q.pups.include? @basename && (not args[:puppywalk]) && (not forced)
        raise D3::InstallError, "#{@basename} (#{D3::PUPPY_Q.q[@basename].edition}) is already queued for puppies. Use force if needed."
    end

    if removable?
      if args[:expiration] && @expiration != args[:expiration]
        @expiration_to_apply = args[:expiration]
        @custom_expiration = args[:expiration]
      else
        @expiration_to_apply = @expiration
        @custom_expiration = false
      end
    else
      # not removable, can't expire
      @expiration_to_apply = 0
    end # if remmovable

    ###
    ### Queue for Puppies
    ###
    if reboot? && (not args[:puppywalk])
      queue_for_puppies forced

    ###
    ### Regular Install...
    ###
    else

      unless forced
        raise D3::InstallError, "#{edition} cannot be installed now because one or more of the following processes is running: #{D3::Admin::OPTIONS[:prohibiting_processes][:display_conversion].call @prohibiting_processes}."  if install_prohibited_by_process?
      end # unless forced

      remove_previous_installs_if_needed (args[:verbose])

      D3.log "Installing: #{edition} (#{@status})#{@using_force}", :warn

      # pre-install script
      pre_install_status = run_pre_install_script(args[:verbose])

      # exit 111 means write receipts, but don't acutally install
      if pre_install_status == 111 then
        D3.log "Pre-install script for #{edition} exited with status '111'; Not installing but writing receipt.", :info
        write_rcpt
        # if this was a puppy install, remove it from the queue
        D3::PUPPY_Q - @basename
        return pre_install_status
      elsif pre_install_status != 0  then
         D3.log  "Pre_install script for #{edition} failed, exit status: #{pre_install_status}, not installing.", :error
        raise D3::PreInstallError,  "Pre_install script for #{edition} failed, exit status: #{pre_install_status}, not installing."
      end # pre_install_status == 111

      # if forced, make the os forget this has been installed before
      if forced and @apple_receipt_data.is_a? Array
        @apple_receipt_data.each do |r|
          D3.log "Forcing OS to forget installer receipt for: #{r[:apple_pkg_id]}", :info
          system "#{JSS::Composer::PKG_UTIL} --forget '#{r[:apple_pkg_id]}' &>/dev/null"
        end # each do r
      end # if force

      # get the read-only passwd for the dist point, if needed
      args[:ro_pw] =  D3::Client.get_ro_pass :dist

      # Install It Already!
      D3.log "Running 'jamf install' of #{edition}", :info

      if install_result = super(args)  # install was good...
        D3.log "Finished 'jamf install' of #{edition}", :debug

        # write our receipt
        write_rcpt

        # if this was a puppy install, remove it from the queue
        D3::PUPPY_Q - @basename

        # run a postflight if needed
        post_install_status = run_post_install_script(args[:verbose])
        if  post_install_status != 0
          D3.log "Post_install script for #{edition} failed, exit status: #{post_install_status}", :error
          raise D3::PostInstallError, "Post_install script for #{edition} failed, exit status: #{post_install_status}"
        end
        D3.log "Done installing #{edition}#{@using_force}", :warn

      else #  bad install
        raise D3::InstallError, "There was a problem installing #{edition}, 'jamf install' failed"
      end # if super args

      return install_result
    end # if reboot?

  ensure
    D3::Client.unset_env :installing
    D3::Client.unset_env :pkg_status
  end # begin...ensure
end

#installed?Boolean

Returns Is this pkg installed on this machine via d3? Note: this overrides JSS::Package#installed?.

Returns:

  • (Boolean)

    Is this pkg installed on this machine via d3? Note: this overrides JSS::Package#installed?



77
78
79
# File 'lib/d3/package/questions.rb', line 77

def installed?
  D3::Client::Receipt.basenames.include?(@basename) and D3::Client::Receipt.all[@basename].id == @id
end

#installed_filesArray<Pathname>

Get an array of files installed by this pkg Note that this does not show directories. use #index to see dirs as well

Returns:

  • (Array<Pathname>)

    the files installed



140
141
142
# File 'lib/d3/package/getters.rb', line 140

def installed_files
  index :files_only
end

#live?Boolean Originally defined in module Basename

Is the status :live?

Returns:

  • (Boolean)

#make_live(admin = @admin) ⇒ void

This method returns an undefined value.

Make this package the live one for its basename

Parameters:

  • admin (String) (defaults to: @admin)

    the name of the admin doing this.

Raises:

  • (JSS::MissingDataError)


148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
# File 'lib/d3/package/server_actions.rb', line 148

def make_live(admin = @admin)

  return :live if @status == :live

  # gotta know who did this
  raise JSS::MissingDataError, "An admin name must be set before making this d3 package live. Use the admin= method." if admin.to_s.empty?
  @admin = admin

  # who and when are we making this pkg live?
  @release_date = Time.now
  @released_by = @admin

  id_field = P_FIELDS[:id][:field_name]
  status_field = P_FIELDS[:status][:field_name]
  basename_field = P_FIELDS[:basename][:field_name]
  rel_date_field =  P_FIELDS[:release_date][:field_name]
  rel_by_field = P_FIELDS[:released_by][:field_name]



  # if any OLDER pkg is live for this basename, make it deprecated
  q =  <<-ENDUPDATE
  UPDATE #{P_TABLE[:table_name]}
    SET #{status_field} = '#{P_FIELDS[:status][:to_sql].call(:deprecated)}'
  WHERE #{basename_field} = '#{to_sql :basename}'
    AND  #{status_field} = '#{P_FIELDS[:status][:to_sql].call(:live)}'
    AND  #{id_field} < '#{to_sql(:id)}'
  ENDUPDATE
  stmt = JSS::DB_CNX.db.prepare q
  stmt_result = stmt.execute

  # now make any older pilot pkgs for this basename :skipped
  q =  <<-ENDUPDATE
  UPDATE #{P_TABLE[:table_name]}
    SET #{status_field} = '#{P_FIELDS[:status][:to_sql].call(:skipped)}'
  WHERE #{basename_field} = '#{to_sql :basename}'
    AND  #{id_field} < #{to_sql(:id)}
    AND  #{status_field} = '#{P_FIELDS[:status][:to_sql].call(:pilot)}'
  ENDUPDATE
  stmt = JSS::DB_CNX.db.prepare q
  stmt_result = stmt.execute


  #  any NEWER pkgs for this basename, become pilot (perhaps again)
  #  This is for when we re-enliven an old pkg
  q =  <<-ENDUPDATE
  UPDATE #{P_TABLE[:table_name]}
    SET #{status_field} = '#{P_FIELDS[:status][:to_sql].call(:pilot)}'
  WHERE #{basename_field} = '#{to_sql :basename}'
    AND  #{id_field} > '#{to_sql(:id)}'
  ENDUPDATE
  stmt = JSS::DB_CNX.db.prepare q
  stmt_result = stmt.execute

  # now make this pkg live
  @status = :live
  q =  <<-ENDUPDATE
  UPDATE #{P_TABLE[:table_name]} SET
    #{status_field} = '#{to_sql :status}',
    #{rel_by_field} = '#{to_sql :released_by}',
    #{rel_date_field} = '#{to_sql :release_date}'
  WHERE #{id_field} = #{@id}
  ENDUPDATE
  stmt = JSS::DB_CNX.db.prepare q
  stmt_result = stmt.execute

  # update our knowledge of the world
  self.class.package_data :refresh

  # while we're writing to the db, mark any missing packages as missing
  mark_missing_packages

  puts "Done '#{edition}' is now live."

  # auto_clean if we should
  auto_clean(admin) if D3::CONFIG.admin_auto_clean

  # run any post-make-live script if needed
  run_make_live_script

end

#mark_missing_packagesvoid

This method returns an undefined value.

Mark missing packages as so on the server

This should run any time we write to the d3_packages table



500
501
502
503
504
505
506
507
508
509
510
511
# File 'lib/d3/package/server_actions.rb', line 500

def mark_missing_packages
  missing_ids = self.class.missing_data.keys
  unless missing_ids.empty?
    q =  <<-ENDUPDATE
    UPDATE #{P_TABLE[:table_name]}
      SET #{ P_FIELDS[:status][:field_name]} = '#{P_FIELDS[:status][:to_sql].call(:missing)}'
    WHERE #{P_FIELDS[:id][:field_name]} IN (#{missing_ids.join(',')})
    ENDUPDATE
    stmt = JSS::DB_CNX.db.prepare q
    stmt_result = stmt.execute
  end # unless empty
end

#missing?Boolean Originally defined in module Basename

Is the status :missing?

Returns:

  • (Boolean)

#mk_index(args = {}) ⇒ void

This method returns an undefined value.

Create, or re-create, the BOM index records for this Package in the JSS Database.

This is the equivalent of clicking the “index” button in Jamf Admin.app, and is necessary for Jamf Pro to be able to uninstall items. It can only happen after the item has already been saved to the JSS and has an id in the database.

Parameters:

  • args (Hash) (defaults to: {})

    The arguments for the method

Options Hash (args):

  • :local_filepath (String, Pathname)

    the path to a local copy of the installer pkg/dmg

  • :ro_pw (String)

    the read-only password for the AFP/SMB share of the master distribution point.

Raises:

  • (JSS::NoSuchItemError)


562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
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
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
# File 'lib/d3/package/server_actions.rb', line 562

def mk_index(args = {})

  raise JSS::NoSuchItemError, "Please create this package in the JSS before indexing it." unless @in_jss
  raise JSS::InvalidConnectionError, "Indexing a package requires a database connection. Use JSS::DB_CNX.connect" unless JSS::DB_CNX.connected?

  if args[:local_filepath]
    file_to_index = Pathname.new(args[:local_filepath])

  elsif args[:ro_pw]
    mdp = JSS::DistributionPoint.master_distribution_point
    file_to_index = mdp.mount(args[:ro_pw], :ro) +"#{DIST_POINT_PKGS_FOLDER}/#{@filename}"
    if file_to_index.to_s.end_with? ".zip"
      tmpdir = Pathname.new "/tmp/jss-tmp-#{$$}"
      system "/usr/bin/unzip '#{thing_to_index}' -d '#{tmpdir}'"
      file_to_index = tmpdir + file_to_index.basename.to_s.sub(/.zip$/, '')
    end

  else
    raise JSS::InvalidDataError, "Need a :local_filepath or :ro_pw"
  end

  # get the index data
  # is it an (m)pkg?
  if file_to_index.to_s =~ /\.m?pkg$/
    bom_lines = ''

    # if the thing is a pkg bundle, find and read all the bom files it contains
    if (file_to_index + "Contents").directory?
      (file_to_index + "Contents").find do |path|
        bom_lines += `echo; /usr/bin/lsbom -p fugTsMc '#{path}'` if path.to_s =~ /\.bom$/
      end # do path

    else
      # else its a flat file - so do it using pkgutil
      bom_files = `/usr/sbin/pkgutil --bom '#{file_to_index}'`
      bom_files.split("\n").each do |file|
        bom_lines += `/usr/bin/lsbom -p fugTsMc '#{file}'`
      end
    end # .directory?

  elsif file_to_index.to_s =~ /\.dmg$/

    # if its a .dmg, mount it, make a tmp bom file, and read that
    mnt_line =  `/usr/bin/hdiutil attach -readonly -nobrowse -noautoopen -owners on '#{file_to_index}'`.lines.last
    mnt_point = Pathname.new mnt_line.split("\t").last.chomp
    raise FileServiceError, "There was a problem mounting the image #{file_to_index}" unless mnt_point.mountpoint?

    tmp_bom = "/tmp/#{@filename}.#{$$}.bom"
    system "/usr/bin/mkbom '#{mnt_point}' '#{tmp_bom}'"
    bom_lines = `/usr/bin/lsbom -p fugTsMc '#{tmp_bom}'`

    system "/usr/bin/hdiutil detach '#{mnt_point}'"
    system "rm -rf '#{tmp_bom}'"

  else
    raise JSS::InvalidDataError, "#{@filename} doesn't looks like a .pkg or .dmg. Try Jamf Admin to index it."
  end # if filename .pkg

  # If there are no bomlines (perhaps a payloadless pkg?) just return
  return true if bom_lines.empty?

  # split the bom lines
  index_records = bom_lines.split "\n"

  # reset our  lists of files
  @index = []
  @file_list = []

  # the start of the SQL insert statement
  insert_stmt = "INSERT INTO package_contents (package_id,file,owner_name,group_name,modification_date,size,mode,checksum) VALUES"
  insert_vals = []

  # loop through the bom data and make a new record for each line
  index_records.each do |line|
    next if line.empty?

    #break out the data for each item
    (path,uid,gid,modtime,size,mode,checksum) = line.split "\t"

    # if the path is just a dot (usually the first one)
    # make it a /
    if path == "."
      clean_path = "/"
    elsif path.start_with? "."
      clean_path = path.sub ".", ""
    else
      clean_path = path
    end

    # rebuild our local lists of files
    @index << { 'path' => clean_path,
    'uid' => uid,
    'gif' => gid,
    'modtime' => modtime,
    'size' => size,
    'mode' => mode }

    @file_list << clean_path unless mode.start_with? "d"

    # JSS stores modtime as string w/o the weekday
    modtime.gsub!(/^(Sun|Mon||Tue|Wed|Thu|Fri|Sat) /, '') if defined? modtime

    insert_vals << "('#{@id}','#{Mysql.quote clean_path}','#{uid}','#{gid}','#{modtime}','#{size}','#{mode}','#{checksum}')"

  end # do line

  # first delete any existing index records for this pkg
  stmt = JSS::DB_CNX.db.prepare "DELETE FROM #{PKG_CONTENTS_TABLE} WHERE package_id = #{@id}"
  stmt_result = stmt.execute

  # now insert the new values
  stmt = JSS::DB_CNX.db.prepare(insert_stmt + " " + insert_vals.join(','))
  stmt_result = stmt.execute

  # while we're writing to the db, mark any missing packages as missing
  mark_missing_packages

  return true
end

#new_script(args = {}) ⇒ Integer

Add or replace a pre- or post- script for this package.

This adds a new script to the JSS, and the sets this package to use it.

If the desired script already exists in the JSS, use an appropriate setter method: #pre_install_script_id=, #post_install_script_id=, #pre_remove_script_id=, #post_remove_script_id=, #pre_install_script_name=,#post_install_script_name=, #pre_remove_script_name=, #post_remove_script_name=

Parameters:

  • args (Hash) (defaults to: {})

Options Hash (args):

  • :script_type (Symbol)

    which script to set? One of :pre_install, :post_install, :pre_remove, :post_remove

  • :source (String, Pathname)

    the script code, or a full path to a file containing the script code. If the value is a String and doesn’t start with a /, it’s considered to be the script code.

  • :script_name (String)

    the name of the new script in the JSS. Defaults to “<basename>-d3<script_type>-YYYYmmddHHMMSS”

  • :script_category (String)

    the name of the JSS category for this script. Defaults to the value of D3:Package::DFT_SCRIPT_CATEGORY

  • :delete_current (Boolean)

    if this new script is replacing one for this pkg, should the old one be deleted from the JSS?

Returns:

  • (Integer)

    the id of the newly created JSS::Script.

Raises:

  • (JSS::InvalidDataError)


254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
# File 'lib/d3/package/server_actions.rb', line 254

def new_script (args = {})

  raise JSS::InvalidDataError, ":script_type must be one of :#{SCRIPT_TYPES.join(', :')}" unless SCRIPT_TYPES.include? args[:script_type]

  args[:script_category] ||= D3::CONFIG.jss_default_script_category
  if args[:script_category]
    raise JSS::NoSuchItemError, "No such category '#{args[:script_category]}' in the JSS." unless JSS::Category.all_names.include? args[:script_category]
  end

  args[:script_name] ||= "#{@basename}-d3#{args[:script_type]}-#{Time.now.strftime('%Y%m%d%H%M%S')}"

  file_source = nil

  file_source = case args[:source]
  when Pathname
    args[:source]
  when String
    Pathname.new(args[:source]) if args[:source].start_with? "/"
  else
    raise JSS::InvalidDataError, ":source must be a full path (Pathname or String), or a String containing the script code."
  end # case

  if file_source
    raise JSS::MissingDataError, "The file #{file_source} is missing or unreadable." unless file_source.readable?
    code = file_source.read
  else
    code = args[:source]
  end

  # get the new script into the JSS
  script = JSS::Script.make :name => args[:script_name]
  script.contents = code
  script.category = args[:script_category]
  new_script_id = script.save

  # update our knowledge of all JSS scripts so the next steps don't fail.
  JSS::Script.all :refresh

  case args[:script_type]
    when :pre_install
      old_script_id = pre_install_script_id
      self.pre_install_script_id = new_script_id
    when :post_install
      old_script_id = post_install_script_id
      self.post_install_script_id = new_script_id
    when :pre_remove
      old_script_id = pre_remove_script_id
      self.pre_remove_script_id = new_script_id
    when :post_remove
      old_script_id = post_remove_script_id
      self.post_remove_script_id = new_script_id
  end

  # delete the old?
  if args[:delete_current] and old_script_id
    JSS::Script.fetch(:id => old_script_id).delete if JSS::Script.all_ids.include? old_script_id
  end

  new_script_id
end

#pilot?Boolean Originally defined in module Basename

Is the status :pilot?

Returns:

  • (Boolean)

#policy_idsArray<Integer>

An Array of ids of all Jamf Pro policies using this package

Returns:

  • (Array<Integer>)

    the policy ids using this package.



148
149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/d3/package/getters.rb', line 148

def policy_ids
  qry = <<-ENDQ
  SELECT p.policy_id
  FROM policies p
  JOIN policy_packages pp
    ON p.policy_id = pp.policy_id
  WHERE pp.package_id = '#{@id}'
  ENDQ
  res = JSS::DB_CNX.db.query qry
  pols = []
  res.each{|r| pols << r[0].to_i }
  res.free
  pols
end

#post_installObject



38
# File 'lib/d3/package/aliases.rb', line 38

alias post_install post_install_script_name

#post_install=(new_val = @post_install_script_id) ⇒ void

This method returns an undefined value.

Set the post_install_script for this package, either by name or JSS id, or a Path (String or Pathname) to a local file.

The script must exist in the JSS or the local file must exist

Parameters:

  • new_val (String, Integer) (defaults to: @post_install_script_id)

    the name or id of the JSS::Script to use



308
309
310
311
312
313
314
315
316
# File 'lib/d3/package/setters.rb', line 308

def post_install= (new_val = @post_install_script_id)
  name_or_path = validate_post_install_script(new_val)
  if name_or_path.is_a?(Pathname)
    @post_install_script_id = new_script script_type: :post_install, source: name_or_path
  else
    @post_install_script_id = JSS::Script.map_all_ids_to(:name).invert[name_or_path]
  end
  @need_to_update_d3 = true unless @initializing
end

#post_install_script=Object



53
# File 'lib/d3/package/aliases.rb', line 53

alias post_install_script= post_install=

#post_install_script?Boolean

Returns Does this pkg have a post-install script?.

Returns:

  • (Boolean)

    Does this pkg have a post-install script?



36
37
38
# File 'lib/d3/package/questions.rb', line 36

def post_install_script?
  not @post_install_script_id.nil?
end

#post_install_script_nameString?

Returns - The name of the post install script for this pkg, or nil if none.

Returns:

  • (String, nil)
    • The name of the post install script for this pkg, or nil if none



39
40
41
42
# File 'lib/d3/package/getters.rb', line 39

def post_install_script_name
  return nil unless @post_install_script_id
  JSS::Script.map_all_ids_to(:name)[@post_install_script_id]
end

#post_install_script_name=Object



63
# File 'lib/d3/package/aliases.rb', line 63

alias post_install_script_name= post_install=

#post_removeObject



40
# File 'lib/d3/package/aliases.rb', line 40

alias post_remove post_remove_script_name

#post_remove=(new_val = @post_remove_script_id) ⇒ void

This method returns an undefined value.

Set the post_remove_script for this package, either by name or JSS id, or a Path (String or Pathname) to a local file.

The script must exist in the JSS or the local file must exist

Parameters:

  • new_val (String, Integer) (defaults to: @post_remove_script_id)

    the name or id of the JSS::Script to use



346
347
348
349
350
351
352
353
354
# File 'lib/d3/package/setters.rb', line 346

def post_remove= (new_val = @post_remove_script_id)
  name_or_path = validate_post_remove_script(new_val)
  if name_or_path.is_a?(Pathname)
    @post_remove_script_id = new_script script_type: :post_remove, source: name_or_path
  else
    @post_remove_script_id = JSS::Script.map_all_ids_to(:name).invert[name_or_path]
  end
  @need_to_update_d3 = true unless @initializing
end

#post_remove_script=Object



55
# File 'lib/d3/package/aliases.rb', line 55

alias post_remove_script= post_remove=

#post_remove_script?Boolean

Returns Does this pkg have a post-remove script?.

Returns:

  • (Boolean)

    Does this pkg have a post-remove script?



46
47
48
# File 'lib/d3/package/questions.rb', line 46

def post_remove_script?
  not @post_remove_script_id.nil?
end

#post_remove_script_nameString?

Returns - The name of the post remove script for this pkg, or nil if none.

Returns:

  • (String, nil)
    • The name of the post remove script for this pkg, or nil if none



53
54
55
56
# File 'lib/d3/package/getters.rb', line 53

def post_remove_script_name
  return nil unless @post_remove_script_id
  JSS::Script.map_all_ids_to(:name)[@post_remove_script_id]
end

#post_remove_script_name=Object



65
# File 'lib/d3/package/aliases.rb', line 65

alias post_remove_script_name= post_remove=

#pre_installObject

aliases for getting the script names



37
# File 'lib/d3/package/aliases.rb', line 37

alias pre_install pre_install_script_name

#pre_install=(new_val = @pre_install_script_id) ⇒ void

This method returns an undefined value.

Set the pre_install_script for this package, either by name or JSS id, or a Path (String or Pathname) to a local file.

The script must exist in the JSS or the local file must exist

Parameters:

  • new_val (String, Integer) (defaults to: @pre_install_script_id)

    the path, name or id of the JSS::Script to use



289
290
291
292
293
294
295
296
297
# File 'lib/d3/package/setters.rb', line 289

def pre_install= (new_val = @pre_install_script_id)
  name_or_path = validate_pre_install_script(new_val)
  if name_or_path.is_a?(Pathname)
    @pre_install_script_id = new_script script_type: :pre_install, source: name_or_path
  else
    @pre_install_script_id = JSS::Script.map_all_ids_to(:name).invert[name_or_path]
  end
  @need_to_update_d3 = true unless @initializing
end

#pre_install_script=Object

aliases for assigning scripts, since assignment methods can take ids, names, or paths clean these up someday!



52
# File 'lib/d3/package/aliases.rb', line 52

alias pre_install_script= pre_install=

#pre_install_script?Boolean

Returns Does this pkg have a pre-install script?.

Returns:

  • (Boolean)

    Does this pkg have a pre-install script?



31
32
33
# File 'lib/d3/package/questions.rb', line 31

def pre_install_script?
  not @pre_install_script_id.nil?
end

#pre_install_script_nameString?

Returns - The name of the pre-install script for this pkg, or nil if none.

Returns:

  • (String, nil)
    • The name of the pre-install script for this pkg, or nil if none



32
33
34
35
# File 'lib/d3/package/getters.rb', line 32

def pre_install_script_name
  return nil unless @pre_install_script_id
  JSS::Script.map_all_ids_to(:name)[@pre_install_script_id]
end

#pre_install_script_name=Object



62
# File 'lib/d3/package/aliases.rb', line 62

alias pre_install_script_name= pre_install=

#pre_removeObject



39
# File 'lib/d3/package/aliases.rb', line 39

alias pre_remove pre_remove_script_name

#pre_remove=(new_val = @pre_remove_script_id) ⇒ void

This method returns an undefined value.

Set the pre_remove_script for this package, either by name or JSS id, or a Path (String or Pathname) to a local file.

The script must exist in the JSS or the local file must exist

Parameters:

  • new_val (String, Integer) (defaults to: @pre_remove_script_id)

    the name or id of the JSS::Script to use



327
328
329
330
331
332
333
334
335
# File 'lib/d3/package/setters.rb', line 327

def pre_remove= (new_val = @pre_remove_script_id)
  name_or_path = validate_pre_remove_script(new_val)
  if name_or_path.is_a?(Pathname)
    @pre_remove_script_id = new_script script_type: :pre_remove, source: name_or_path
  else
    @pre_remove_script_id = JSS::Script.map_all_ids_to(:name).invert[name_or_path]
  end
  @need_to_update_d3 = true unless @initializing
end

#pre_remove_script=Object



54
# File 'lib/d3/package/aliases.rb', line 54

alias pre_remove_script= pre_remove=

#pre_remove_script?Boolean

Returns Does this pkg have a pre-remove script?.

Returns:

  • (Boolean)

    Does this pkg have a pre-remove script?



41
42
43
# File 'lib/d3/package/questions.rb', line 41

def pre_remove_script?
  not @pre_remove_script_id.nil?
end

#pre_remove_script_nameString?

Returns - The name of the pre remove script for this pkg, or nil if none.

Returns:

  • (String, nil)
    • The name of the pre remove script for this pkg, or nil if none



46
47
48
49
# File 'lib/d3/package/getters.rb', line 46

def pre_remove_script_name
  return nil unless @pre_remove_script_id
  JSS::Script.map_all_ids_to(:name)[@pre_remove_script_id]
end

#pre_remove_script_name=Object



64
# File 'lib/d3/package/aliases.rb', line 64

alias pre_remove_script_name= pre_remove=

#prohibiting_processes=(new_val = @prohibiting_processes) ⇒ void

This method returns an undefined value.

Set the prohibiting processes for this installer.

The value of this attribute is compared at install time to the lines output by the command ‘ps -A -c -o comm’ (case insensitive)

If any line matches, d3 will attempt to quit the process (or prompt the user to quit the Application) to proceed with installation.

Parameters:

  • new_val (Array<String>) (defaults to: @prohibiting_processes)

    the process name(s) that should not be running during installation.



175
176
177
178
179
180
# File 'lib/d3/package/setters.rb', line 175

def prohibiting_processes= (new_val = @prohibiting_processes)
  new_val = JSS.to_s_and_a(new_val)[:arrayform]
  return @prohibiting_processes if D3::Admin::OPTIONS[:prohibiting_processes][:compare].call(@prohibiting_processes, new_val)
  @prohibiting_processes = new_val.each {|process| validate_prohibiting_process process}
  @need_to_update_d3 = true unless @initializing
end

#queue_for_puppies(force = D3::forced?) ⇒ Object

This just queues this installer for installation at the next puppywalk For now, we’re intentionally NOT caching the installer for off-line installation. The puppy installer will only run when the machine can talk to the JSS



231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
# File 'lib/d3/package/client_actions.rb', line 231

def queue_for_puppies (force = D3::forced?)

    # create the puppy
    new_pup = D3::PuppyTime::PendingPuppy.new( :basename => @basename,
      :version => @version,
      :revision => @revision,
      :admin => @admin,
      :force => force,
      :custom_expiration => @custom_expiration,
      :status => @status )

    # add it to the queue - this will return true or false
    added_2_q = D3::PUPPY_Q + new_pup

    # tell someone
    D3.log "Added #{edition} (#{@status}) to the puppy queue#{@using_force}", :warn if added_2_q

    return true
end

#releaseObject



68
# File 'lib/d3/package/aliases.rb', line 68

alias release make_live

#remove_auto_groups(groupnames) ⇒ void

This method returns an undefined value.

Remove one or more groups the to list of auto_groups. The arg is a comma-separated string or an array of group names.

Parameters:

  • groupnames (String, Array)

    the names of the groups to remove



225
226
227
228
229
# File 'lib/d3/package/setters.rb', line 225

def remove_auto_groups (groupnames)
  remove_groups = JSS.to_s_and_a(groupnames)[:arrayform]
  @auto_groups -= remove_groups
  @need_to_update_d3 = true unless @initializing
end

#remove_excluded_groups(groupnames) ⇒ void

This method returns an undefined value.

Remove one or more groups the to list of excluded_groups. The arg is a comma-separated string or an array of group names.

Parameters:

  • groupnames (String, Array)

    the names of the groups to remove



274
275
276
277
278
# File 'lib/d3/package/setters.rb', line 274

def remove_excluded_groups (groupnames)
  remove_groups = JSS.to_s_and_a(groupnames)[:arrayform]
  @excluded_groups -= remove_groups
  @need_to_update_d3 = true unless @initializing
end

#remove_expiration_path(path) ⇒ Object

Remove a path from expiration_paths The paths are those recorded in d3RepoMan’s timestamp plists.

Examples:

“/Applications/FileMaker Pro 11/FileMaker Pro.app/Contents/MacOS/Filemaker Pro”

Parameters:

  • new_val (String, Pathname)


157
158
159
160
# File 'lib/d3/package/setters.rb', line 157

def remove_expiration_path (path)
  @expiration_paths.delete Pathname.new(path)
  @need_to_update_d3 = true unless @initializing
end

#revision=(new_val = @revision) ⇒ Object

Set the basename of this package

new_val = string



71
72
73
74
75
76
77
# File 'lib/d3/package/setters.rb', line 71

def revision= (new_val = @revision)
  return nil if new_val == @revision
  new_val = validate_revision new_val
  validate_edition "#{basename}-#{@version}-#{new_val}"
  @revision = new_val
  @need_to_update_d3 = true unless @initializing
end

#run_make_live_scriptProcess::Status

Run the make_live script, if any. We do this by creating and runngin a tmp file, rather than using the jamf binary, because this wont’ be done as root, so the jamf binary can’t be run.

Returns:

  • (Process::Status)

    the status of the finished script.



36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/d3/package/client_actions.rb', line 36

def run_make_live_script
  # Run the make_live script if any
  if script = D3::CONFIG.admin_make_live_script
    if JSS::Script.all_names.include? script
      code = JSS::Script.fetch(name: script).code
    elsif JSS::Script.all_ids.include? script
      code = JSS::Script.fetch(id: script).code
    else
      return nil
    end

    return nil unless code
    return nil unless code.start_with? "#!"

    tmp_file =  Pathname.new Tempfile.new("mklive")
    tmp_file.jss_touch
    tmp_file.chmod 0700
    tmp_file.jss_save code

    ENV['D3_MAKE_LIVE_EDITION'] = edition
    ENV['D3_MAKE_LIVE_ADMIN'] = @admin
    ENV['D3_MAKE_LIVE_DESC'] = description
    ENV['D3_MAKE_LIVE_AUTO_GROUPS'] = auto_groups.join(',')
    ENV['D3_MAKE_LIVE_EXCL_GROUPS'] = excluded_groups.join(',')

    system tmp_file.to_s
    tmp_file.delete

    ENV['D3_MAKE_LIVE_EDITION'] = nil
    ENV['D3_MAKE_LIVE_ADMIN'] = nil
    ENV['D3_MAKE_LIVE_DESC'] = nil
    ENV['D3_MAKE_LIVE_AUTO_GROUPS'] = nil
    ENV['D3_MAKE_LIVE_EXCL_GROUPS'] = nil
  end
end

#run_post_install_script(verbose = false) ⇒ Integer?

Run the post-install script, if any.

Parameters:

  • verbose (Boolean) (defaults to: false)

    Should the script be run verbosely?

Returns:

  • (Integer, nil)

    The exitstatus of the script, 0 if no script



278
279
280
281
282
283
284
285
286
287
288
289
290
291
# File 'lib/d3/package/client_actions.rb', line 278

def run_post_install_script(verbose = false)
  return 0 unless post_install_script?
  begin
    D3::Client.set_env :post_install, edition
    D3.log "Running post_install script for #{edition}", :info
    (exit_status, output) = JSS::Script.fetch(:id => @post_install_script_id).run :verbose => verbose, :show_output => verbose
    D3.log "Finished post_install script for #{edition}", :debug
  rescue D3::ScriptError
    raise PostInstallError, $!
  ensure
    D3::Client.unset_env :post_install
  end # begin
  return exit_status
end

#run_pre_install_script(verbose = false) ⇒ Integer?

Run the pre-install script, if any.

Parameters:

  • verbose (Boolean) (defaults to: false)

    Should the script be run verbosely?

Returns:

  • (Integer, nil)

    The exitstatus of the script, 0 if no script



257
258
259
260
261
262
263
264
265
266
267
268
269
270
# File 'lib/d3/package/client_actions.rb', line 257

def run_pre_install_script(verbose = false)
  return 0 unless pre_install_script?
  begin
    D3::Client.set_env :pre_install, edition
    D3.log "Running pre_install script for #{edition}", :info
    (exit_status, output) = JSS::Script.fetch(:id => @pre_install_script_id).run :verbose => verbose, :show_output => verbose
    D3.log "Finished pre_install script for #{edition}", :debug
  rescue D3::ScriptError
    raise PreInstallError, $!
  ensure
    D3::Client.unset_env :pre_install
  end # begin
  return exit_status
end

#saveObject

An alias for both save and update



134
135
136
137
138
139
140
# File 'lib/d3/package/server_actions.rb', line 134

def save
  if @in_jss
    update # this will create the d3 data if needed
  else
    create
  end
end

#saved?Boolean

Returns Is this pkg on the server?.

Returns:

  • (Boolean)

    Is this pkg on the server?



58
59
60
# File 'lib/d3/package/questions.rb', line 58

def saved?
  not (@need_to_update or @need_to_update_d3)
end

#script_idsHash{Symbol=>Integer}

Returns The type and ids of all pre- and post- scripts for this pkg.

Returns:

  • (Hash{Symbol=>Integer})

    The type and ids of all pre- and post- scripts for this pkg.



60
61
62
63
64
65
66
67
# File 'lib/d3/package/getters.rb', line 60

def script_ids
  {
    :pre_install => @pre_install_script_id,
    :post_install => @post_install_script_id,
    :pre_remove => @pre_remove_script_id,
    :post_remove => @post_remove_script_id
  }
end

#script_namesHash{Symbol=>String}

Returns The type and names of all pre- and post- scripts for this pkg.

Returns:

  • (Hash{Symbol=>String})

    The type and names of all pre- and post- scripts for this pkg.



71
72
73
74
75
76
77
78
# File 'lib/d3/package/getters.rb', line 71

def script_names
  {
    :pre_install => @pre_install_script_name,
    :post_install => @post_install_script_name,
    :pre_remove => @pre_remove_script_name,
    :post_remove => @post_remove_script_name
  }
end

#skipped?Boolean

Returns Is this pkg in pilot? (saved, but never made live).

Returns:

  • (Boolean)

    Is this pkg in pilot? (saved, but never made live)



70
71
72
# File 'lib/d3/package/questions.rb', line 70

def skipped?
  @status == :skipped
end

#updateInteger

Update this package in the JSS and in d3

Returns:

  • (Integer)

    the JSS id of the package



88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/d3/package/server_actions.rb', line 88

def update

  # we might be importing an existing JSS pkg to d3, which
  # means we need to create the d3 record, but the JSS record needs updating
  create if @importing and (not @in_d3)

  # update the JSS first, if needed
  super

  # and return unless we need to do something.
  return unless  @need_to_update_d3

  # Loop thru the field defs to build the SQL update statement
  new_vals = []
  P_FIELDS.each_pair do |key,field_def|

    # start builing the SET clause values, e.g. "basename = 'foobar'"
    field_val = "#{field_def[:field_name]} = "

    # finish the SET clause value
    field_val << (self.send(key).to_s.empty? ? 'NULL' : "'#{to_sql(key)}'")

    # add it to the array
    new_vals << field_val
  end # do |key,field_def

  # use the new_vals array to create the update statement
  stmt = JSS::DB_CNX.db.prepare <<-ENDUPDATE
  UPDATE #{P_TABLE[:table_name]} SET
    #{new_vals.join(",  ")}
  WHERE
    #{P_FIELDS[:id][:field_name]} = #{@id}
  ENDUPDATE

  # Execute it to update the record
  stmt_result = stmt.execute

 # while we're writing to the db, mark any missing packages as missing
  mark_missing_packages

  return @id

end

#update_apple_receipt_data(dist_pw, unmount = true) ⇒ void

This method returns an undefined value.

Learn the apple package id’s installed by this pkg by querying the package on the current dist. point. This is primarily used for importing or repairing packages already on the server.

When adding new packages, the #upload_master_file method will query the data before uploading the file.

Parameters:

  • dist_pw (String)

    the read-only or read-write password for the dist. point for this machine

  • unmount (Boolean) (defaults to: true)

    should the dist.point be unmounted when done?

Raises:

  • (JSS::NoSuchItemError)


459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
# File 'lib/d3/package/server_actions.rb', line 459

def update_apple_receipt_data(dist_pw, unmount = true)
  return nil if @filename.end_with? ".dmg"
  raise JSS::NoSuchItemError, "Please create this package on the server before updating the Apple receipt data" unless @in_jss

  mdp = JSS::DistributionPoint.my_distribution_point
  raise JSS::MissingDataError, "Missing :dist_pw for distrib. point '#{mdp.name}'" unless dist_pw

  # try the passwd both with ro and rw
  begin
    mnt_path = mdp.mount(dist_pw, :ro)
  rescue JSS::InvalidDataError
    mnt_path = mdp.mount(dist_pw, :rw)
  end

  pkg_path = mnt_path + JSS::Package::DIST_POINT_PKGS_FOLDER + @filename
  raise JSS::NoSuchItemError, "Package file #{@filename} doesn't exist on the current dist. point." unless pkg_path.exist?

  pkg_to_query = pkg_path

  # do we need to unzip a bundle pkg?
  if @filename.end_with? ".zip"
    work_dir = Pathname.new Dir.mktmpdir
    unless system "/usr/bin/unzip -qq -o -d #{Shellwords.escape work_dir.to_s} #{Shellwords.escape pkg_path.to_s}"
      raise RuntimeError, "Failed to unzip bundle pkg #{@filename}"
    end #system
    pkg_to_query = work_dir +  @filename.sub(/\.zip$/, '')
    cleanup_work_dir = true
  end # if @filename.end_with? ".zip"

  @apple_receipt_data = D3::Package.receipt_data_from_pkg(pkg_to_query)
  @need_to_update_d3 = true unless @initializing
  work_dir.rmtree if cleanup_work_dir
  mdp.unmount if unmount
end

#upload_master_file(local_file_path, rw_pw, unmount = true) ⇒ void

This method returns an undefined value.

Upload a locally-readable file to the master distribution point. If the file is a directory (like a bundle .pk/.mpkg) it will be zipped before uploading and the @filename will be adjusted accordingly

If you’ll be uploading several files you can specify unmount as false, and do it manually when all are finished with JSS::DistributionPoint.master_distribution_point.unmount

This method is mostly performed by the parent class, see JSS::Package.upload_master_file. Before calling super, this method populates @apple_receipt_data with info from the local file.

Parameters:

  • local_file_path (String, Pathname)

    the local path to the file to be uploaded

  • rw_pw (String, Symbol)

    the password for the read/write account on the master Distribution Point, or :prompt, or :stdin# where # is the line of stdin containing the password See JSS::DistributionPoint#mount

  • unmount (Boolean) (defaults to: true)

    whether or not ot unount the distribution point when finished.

Raises:

  • (JSS::NoSuchItemError)


533
534
535
536
537
538
539
540
541
# File 'lib/d3/package/server_actions.rb', line 533

def upload_master_file (local_file_path, rw_pw, unmount = true)
  raise JSS::NoSuchItemError, "Please create this package in d3 before uploading it." unless @in_d3
  if local_file_path.to_s =~ PKG_RE
    @apple_receipt_data = D3::Package.receipt_data_from_pkg(local_file_path)
    @need_to_update_d3 = true
  end # if local_file_path.to_s =~ PKG_RE
  super
  update
end

#validate_auto_groups(groups) ⇒ Object Originally defined in module Validate

See Also:

#validate_category(cat) ⇒ String Originally defined in module Validate

Check the validity of a category name Raise an exception if not valid. nil and empty strings are acceptable to unset the category.

Parameters:

  • cat (String)

    the category to check

Returns:

  • (String)

    the valid category name

Raises:

  • (JSS::NoSuchItemError)

#validate_cpu_type(type) ⇒ Symbol Originally defined in module Validate

Check the validity of a CPU type Raise an exception if not valid

Parameters:

  • the (Symbol)

    CPU type to check

Returns:

  • (Symbol)

    the valid CPU type

#validate_edition(edition) ⇒ String Originally defined in module Validate

Check if an edition exists and raise an exception if so Also check that it contains at least two hyphens

Parameters:

  • edition (String)

    the edition to check

Returns:

  • (String)

    the valid, unique edition

Raises:

  • (JSS::AlreadyExistsError)

#validate_excluded_groups(groups) ⇒ Object Originally defined in module Validate

See Also:

#validate_expiration(exp) ⇒ Integer Originally defined in module Validate

Confirm the validity of an expiration. Raise an exception if invalid.

Parameters:

  • exp (Integer)

    the expiration to check

Returns:

  • (Integer)

    the valid expiration

Raises:

  • (JSS::InvalidDataError)

#validate_expiration_path(path) ⇒ Pathname Originally defined in module Validate

Confirm the validity of an expiration path. Any string that starts with a / is valid, d3 can’t confirm the paths existing on client machines.

Parameters:

  • paths (Pathname, String)

    the path to check

Returns:

  • (Pathname)

    the valid path

Raises:

  • (JSS::InvalidDataError)

#validate_expiration_paths(paths) ⇒ Array<Pathname> Originally defined in module Validate

Confirm the validity of one or more expiration paths. Any string that starts with a / is valid. The strings “n” or “none” returns an empty array.

Parameters:

  • paths (Pathname, String, Array<String,Pathname>)

    the path(s) to check

Returns:

  • (Array<Pathname>)

    the valid path

#validate_filename(name) ⇒ String Originally defined in module Validate

check that the given filename doesn’t already exist

Parameters:

  • name (String)

    the name to check

Returns:

  • (String)

    the valid new file name

Raises:

  • (JSS::AlreadyExistsError)

#validate_groups(groups, check_for_std = false) ⇒ Array Originally defined in module Validate

Confirm the existence of a list of Computer Group names (String or Array) and return them as an Array

If “n”, “”, “none” or nil are passed, an empty array is returned.

Raise an exception if any group is not valid.

Parameters:

  • groups (String, Array<String>)
  • check_for_std (Boolean) (defaults to: false)

    should we check for the D3::STANDARD_AUTO_GROUP?

Returns:

  • (Array)

    The valid groups as an array

#validate_non_overlapping_groups(auto, excl) ⇒ True Originally defined in module Validate

Make sure auto and excluded groups don’t have any members in common, raise an exception if they do.

Parameters:

  • auto (Array)

    the array of auto groups

  • excl (Array)

    the array of excluded groups

Returns:

  • (True)

    true if there are no groups in common

Raises:

  • (JSS::InvalidDataError)

#validate_oses(os_list) ⇒ Array Originally defined in module Validate

Check the validity of a list of OSes Raise an exception if not valid

Parameters:

  • Array (String, Array)

    or comma-separated list of OSes to check

Returns:

  • (Array)

    the valid OS list

#validate_package_name(name) ⇒ Object Originally defined in module Validate

check that the given package name doesn’t already exist

Raises:

  • (JSS::AlreadyExistsError)

See Also:

  • D3::Package::Validate.{JSS{JSS::Package{JSS::Package.validate_package_name}

#validate_post_install_script(script) ⇒ Object Originally defined in module Validate

Check the validity of a post_install script

See Also:

#validate_post_remove_script(script) ⇒ Object Originally defined in module Validate

Check the validity of a pre_remove script

See Also:

#validate_pre_install_script(script) ⇒ Object Originally defined in module Validate

Check the validity of a pre_install script

See Also:

#validate_pre_remove_script(script) ⇒ Object Originally defined in module Validate

Check the validity of a pre_remove script

See Also:

#validate_prohibiting_process(process_name) ⇒ String Originally defined in module Validate

Check a single prohibiting process for validity

Parameters:

  • process_name (String)

    the process to be validated.

Returns:

  • (String)

#validate_revision(rev) ⇒ Integer Originally defined in module Validate

Confirm the validity of a revision. Raise an exception if invalid.

Parameters:

  • rev (Integer)

    the revision to check

Returns:

  • (Integer)

    the valid revision

Raises:

  • (JSS::InvalidDataError)

#validate_script(script) ⇒ Pathname, String Originally defined in module Validate

Check the validity of a script, either Pathname, JSS id, or JSS name Raise an exception if not valid

Parameters:

  • script (Pathname, Integer, String)

    the script to check

Returns:

  • (Pathname, String)

    the valid local path or JSS name of the script

#validate_version(vers) ⇒ String Originally defined in module Validate

Confirm the validity of a version. Raise an exception if invalid.

Parameters:

  • vers (String)

    the version to check

Returns:

  • (String)

    An error message, or true if the value is ok

Raises:

  • (JSS::InvalidDataError)

#validate_yes_no(yn) ⇒ Boolean Originally defined in module Validate

check the validity of a yes/no,true/false,1/0 input value

TrueClass, “true”, “y”,“yes”, and 1 are true

FalseClass, nil, “false”, “n”, “no”, and 0 are false

(Strings are case-insensitive) Anything else raises an exception.

Parameters:

  • type (String, Boolean, Integer)

    the value to check

Returns:

  • (Boolean)

Raises:

  • (JSS::InvalidDataError)

#version=(new_val = @version) ⇒ Object

Set the version of this package

new_val = string



59
60
61
62
63
64
65
# File 'lib/d3/package/setters.rb', line 59

def version= (new_val = @version)
  return nil if new_val == @version
  new_val = validate_version new_val
  validate_edition "#{basename}-#{new_val}-#{@revision}"
  @version = new_val
  @need_to_update_d3 = true unless @initializing
end