Module: JSS::Composer

Defined in:
lib/jss-api.rb,
lib/jss-api/composer.rb

Overview

This module provides two methods for building very simple Casper-happy .pkg and .dmg packages for deployment.

Unlike Composer.app from JAMF, this module currently doesn’t offer a way to do a before/after disk scan and use the differences to build the root folder from which the package is built. Nor does the module support editing the pre/post install scripts in .pkgs.

The ‘root folder’, a folder representing the root filesystem of the target machine where the package will be installed, must already exist and be fully populated and with correct permissions.

Constant Summary collapse

PKG_UTIL =

the apple pkgutil tool

Pathname.new "/usr/sbin/pkgutil"
PKGBUILD =

The location of the cli tool for making .pkgs

Pathname.new "/usr/bin/pkgbuild"
PKG_BUNDLE_ID_PFX =

the default bundle identifier prefix for pkgs

'jss_gem_composer'
HDI_UTIL =

Apple’s hdiutil for making dmgs

'/usr/bin/hdiutil'
DEFAULT_OUT_DIR =

Where to save the output ?

Pathname.new "/Users/Shared"

Class Method Summary collapse

Class Method Details

.mk_dmg(name, root, out_dir = DEFAULT_OUT_DIR) ⇒ Pathname

Make a casper-happy .dmg out of a root folder, permissions are assumed to be correct.

Parameters:

  • name (String)

    The name of the .dmg, the suffix will be added if needed

  • root (String, Pathname)

    the path to the “root folder” representing the root file system of the target install drive

  • out_dir (String, Pathname) (defaults to: DEFAULT_OUT_DIR)

    the folder in which the .pkg will be created. Defaults to DEFAULT_OUT_DIR

Returns:

  • (Pathname)

    the local path to the new .dmg

Raises:

  • (RuntimeError)


154
155
156
157
158
159
160
161
162
163
164
165
# File 'lib/jss-api/composer.rb', line 154

def self.mk_dmg(name, root, out_dir = DEFAULT_OUT_DIR)

  dmg_filename = "#{name}.dmg"
  dmg_vol = name
  dmg_out = "#{out_dir}/#{dmg_filename}"

  system "#{HDI_UTIL} create -volname '#{dmg_vol}' -srcfolder '#{root}' '#{dmg_out}'"

  raise RuntimeError, "There was an error building the .dmg" unless $?.exitstatus == 0
  return Pathname.new dmg_out

end

.mk_pkg(name, version, root, opts = {}) ⇒ Pathname

Make a casper-happy .pkg out of a root folder, permissions are assumed to be correct.

Parameters:

  • name (String)

    the name of the .pkg. The .pkg suffix will be added if not present

  • version (String)

    the version of the .pkg, needed for building the .pkg

  • root (String, Pathname)

    the path to the “root folder” representing the root file system of the target install drive

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

    the options for building the .pkg

Options Hash (opts):

  • :bundle_id_prefix (String)

    the pkg bundle identifier prefix. e.g. ‘org.some.organization’ or ‘com.pixar.d3’. Defaults to ‘PKG_BUNDLE_ID_PFX’. See ‘man pkgbuild’ for more info

  • :out_dir (String, Pathname)

    he folder in which the .pkg will be created. Defaults to DEFAULT_OUT_DIR

  • :preserve_ownership (Boolean)

    If true, the owner/group of the rootpath are preserved. Default is false: they become the pkgbuild/installer “recommended” (root/wheel or root/admin)

Returns:

  • (Pathname)

    the local path to the new .pkg

Raises:



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
# File 'lib/jss-api/composer.rb', line 83

def self.mk_pkg(name, version, root, opts = {})
  raise NoSuchItemError, "Missing pkgbuild tool. Please make sure you're running 10.8 or later." unless PKGBUILD.executable?

  opts[:out_dir] ||= DEFAULT_OUT_DIR
  opts[:bundle_id_prefix] ||= PKG_BUNDLE_ID_PFX

  pkg_filename = "#{name}.pkg"
  pkg_id = opts[:bundle_id_prefix] + "." + name
  pkg_out = "#{opts[:out_dir]}/#{pkg_filename}"
  pkg_ownership = opts[:preserve_ownership] ? "preserve" : "recommended"


  ### first, run 'analyze' to get a 'component plist' in which we can change some settings
  ### for any bundles in the root (bundles like .apps, frameworks, plugins, etc..)
  ###
  ### we edit the settings thus:
  ### BundleOverwriteAction = upgrade, totally replace any version current on disk
  ### BundleIsVersionChecked = false, allow us to install regardless of what version is currently installed
  ### BundleIsRelocatable = false,  if there's a version of this in some other location, Do Not move this one there after installation
  ### BundleHasStrictIdentifier = false, don't care if there's something at the install path with a different bundle id.
  ###
  ### In other words, just install the thing!
  ### (see 'man pkgbuild' for more info)
  ###
  ###
  comp_plist_out = Pathname.new "/tmp/#{PKG_BUNDLE_ID_PFX}-#{pkg_filename}.plist"
  system "#{PKGBUILD} --analyze --root '#{root}' '#{comp_plist_out}'"
  comp_plist = Plist.parse_xml comp_plist_out.read

  ### if the plist is empty, there are no bundles in the pkg
  if comp_plist[0].nil?
    comp_plist_arg = ''
  else
    ### otherwise, edit the bundle dictionaries
    comp_plist.each do |bndl|
      bndl.delete "ChildBundles" if bndl["ChildBundles"]
      bndl["BundleOverwriteAction"] = "upgrade"
      bndl["BundleIsVersionChecked"] = false
      bndl["BundleIsRelocatable"] = false
      bndl["BundleHasStrictIdentifier"] = false
    end
    ### write out the edits
    comp_plist_out.open('w'){|f| f.write comp_plist.to_plist}
    comp_plist_arg = "--component-plist '#{comp_plist_out}'"
  end

  ### now build the pkg
  begin
    system "#{PKGBUILD} --identifier '#{pkg_id}' --version '#{version}' --ownership #{pkg_ownership} --install-location / --root '#{root}' #{comp_plist_arg} '#{pkg_out}' "

    raise RuntimeError, "There was an error building the .pkg" unless $?.exitstatus == 0
  ensure
    comp_plist_out.delete if comp_plist_out.exist?
  end

  return Pathname.new pkg_out
end