Module: Xolo::Server::Helpers::FileTransfers

Defined in:
lib/xolo/server/helpers/file_transfers.rb

Constant Summary collapse

UPLOAD_ACTION =

Constants

'Upload'
REUPLOAD_ACTION =
'Re-upload'
CLOUD_DP_NA =

Thes values in the cdnType field of the Cloud DP definition from the API indicate that there is no Cloud DP configured

[nil, Xolo::BLANK, 'NONE'].freeze
CLOUD_DP_UPLOAD_TIMEOUT =

How long to wait for a pkg to appear on the Cloud DP when uploading via API before giving up and raising an error

1800

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.included(includer) ⇒ Object

when this module is included



39
40
41
# File 'lib/xolo/server/helpers/file_transfers.rb', line 39

def self.included(includer)
  Xolo.verbose_include includer, self
end

Instance Method Details

#cloud_dp_available?Boolean

Returns Is a cloud distribution point defined?.

Returns:

  • (Boolean)

    Is a cloud distribution point defined?



530
531
532
# File 'lib/xolo/server/helpers/file_transfers.rb', line 530

def cloud_dp_available?
  !CLOUD_DP_NA.include? cloud_dp_data[:cdnType]
end

#cloud_dp_dataHash

TODO: Use ruby-jss when it implements could-distribution-point rsrc

Returns:

  • (Hash)

    The Cloud DP definition from the API, if available, minus the keyPairId & privateKey If no cloud dp defined, returns { cdnType: ‘NONE’, master: false }



514
515
516
517
518
519
520
521
522
523
524
525
526
# File 'lib/xolo/server/helpers/file_transfers.rb', line 514

def cloud_dp_data
  return @cloud_dp_data if @cloud_dp_data

  @cloud_dp_data = jamf_cnx.jp_get '/v1/cloud-distribution-point'
  @cloud_dp_data.delete :privateKey
  @cloud_dp_data.delete :keyPairId
  @cloud_dp_data
rescue Jamf::Connection::JamfProAPIError => e
  @cloud_dp_data = { cdnType: 'NONE', master: false }
  return @cloud_dp_data if jamf_cnx.last_http_response.status == 404

  raise e
end

#cloud_dp_pkg_ready?(pkg_filename) ⇒ Boolean

TODO: Use ruby-jss when it implements could-distribution-point rsrc

Does a given pkg name exist on the cloud dp with ‘ready’ status?

Parameters:

  • pkg_name (String)

    the name of the pkg to look for

Returns:

  • (Boolean)

    Is the pkg ready-to-go on the Cloud DP?



549
550
551
552
553
554
555
556
557
558
559
560
561
562
# File 'lib/xolo/server/helpers/file_transfers.rb', line 549

def cloud_dp_pkg_ready?(pkg_filename)
  return false unless cloud_dp_available?

  filt = CGI.escape "fileName=='#{pkg_filename}'"
  response = jamf_cnx.jp_get "/v1/cloud-distribution-point/files?filter=#{filt}"

  # No fileserver I know of will allow multiples of a single filename....
  # so assume there's only zero or one
  data = response[:results].first
  return false unless data

  # once the status is ready, we should be good to go
  data[:status] == 'READY'
end

#cloud_dp_pkgsHash {String => String}

TODO: Use ruby-jss when it implements could-distribution-point rsrc

Returns:

  • (Hash {String => String})

    The Jamf ID => FileName of all ‘READY’ PACKAGE files on the Cloud DP.



569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
# File 'lib/xolo/server/helpers/file_transfers.rb', line 569

def cloud_dp_pkgs
  page = 0
  page_size = 1000
  pkgs = {}
  loop do
    response = jamf_cnx.jp_get "/v1/cloud-distribution-point/files?page=#{page}&page-size=#{page_size}"
    results = response[:results]
    break if results.empty?

    results.each do |f|
      next unless f[:type] == 'PACKAGE' && f[:status] == 'READY'

      # fileObjectId is the Jamf ID of the Package object for this DP file
      pkgs[f[:fileObjectId]] = f[:fileName]
    end
    page += 1
  end
  pkgs
end

#cloud_dp_principal?Boolean

TODO: Use ruby-jss when it implements could-distribution-point rsrc

Returns:

  • (Boolean)

    Is a cloud distribution point defined?



537
538
539
# File 'lib/xolo/server/helpers/file_transfers.rb', line 537

def cloud_dp_principal?
  cloud_dp_available? && cloud_dp_data[:master]
end

#dist_pkg_filename(version) ⇒ String

What will be the name of the file on the dist point? For a first upload, it will be ‘xolo-<title>-<version>.pkg’

If we are re-uploading, it will be ‘xolo-<title>-<version>N.pkg’ where N is an integer (starting with 2) that increments with each re-upload,

This is so that re-uploads don’t have the same filename on the Dist Point, which could be problematic. It also helps to visually see that this is a re-uploaded pkg and not the original one.

Parameters:

Returns:

  • (String)

    the filename to use for the pkg on the dist point



321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
# File 'lib/xolo/server/helpers/file_transfers.rb', line 321

def dist_pkg_filename(version)
  if version.upload_date.pix_empty?
    # no upload date, this is the first upload
    "#{version.jamf_pkg_name}#{Xolo::DOT_PKG}"

  elsif version.jamf_pkg_file.to_s =~ /_(\d+)_\.pkg$/
    # this is a re-upload, does the filename indicat a previous re-upload with a _N_ in the name?
    next_num = Regexp.last_match[1].to_i + 1
    "#{version.jamf_pkg_name}_#{next_num}_#{Xolo::DOT_PKG}"

  else
    # the first re-upload, just add _2_ before the extension
    "#{version.jamf_pkg_name}_2_#{Xolo::DOT_PKG}"
  end
end

#pkg_is_distribution?(pkg_file) ⇒ Boolean

Check if a package is a Distribution package, if not, it is a component package and can’t be used for MDM deployment.

Parameters:

  • pkg_file (Pathname, String)

    The path to the .pkg

Returns:

  • (Boolean)

    true if the pkg is a Distribution package

Raises:

  • (ArgumentError)


365
366
367
368
369
370
# File 'lib/xolo/server/helpers/file_transfers.rb', line 365

def pkg_is_distribution?(pkg_file)
  pkg_file = Pathname.new(pkg_file)
  raise ArgumentError, "pkg_file does not exist or not a file: #{pkg_file}" unless pkg_file.file?

  `/usr/bin/xar -tf #{pkg_file.to_s.shellescape}`.split("\n").include? 'Distribution'
end

#prep_pkg_for_upload(version, pkg_src) ⇒ Pathname

Prep a .pkg before we start uploading to the dist point

Parameters:

  • version (Xolo::Server::Version)

    the version that is being uploaded/re-uploaded

  • pkg_src (Pathname)

    the path to the file to be uploaded to Jamf

Returns:

  • (Pathname)

    the path to the staged pkg that is ready to be uploaded to Jamf



146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/xolo/server/helpers/file_transfers.rb', line 146

def prep_pkg_for_upload(version, pkg_src)
  msg = "Jamf: Processing installer package '#{pkg_src}' (#{pkg_src.size.pix_humanize_bytes}) for Jamf Dist upload, title '#{version.title}' version '#{version.version}'"
  progress msg, log: :info

  version.jamf_pkg_file = dist_pkg_filename(version)
  log_debug "Jamf: Uploaded package filename will be '#{version.jamf_pkg_file}'"
  version.save_local_data

  # The pkg_src will be staged here before uploading to the Dist Point
  staged_pkg = Xolo::Server::Title.title_dir(version.title) + version.jamf_pkg_file

  # remove any old one that might be there
  staged_pkg.delete if staged_pkg.file?

  # This will move/copy the pkg_src into the staged_pkg, signing it on the way if needed, and
  # delete the original pkg_src file.
  sign_and_stage(pkg_src, staged_pkg, version)

  # Wrap component pkgs in a Distribution pkg if configured to do so
  staged_pkg = wrap_component_pkg_in_distribution(staged_pkg, version) if Xolo::Server.config.create_distribution_pkgs

  staged_pkg
end

#process_and_upload_autopkg_pkg(title, version, pkg_src) ⇒ void

This method returns an undefined value.

upload a package from autopkg to Jamf Pro and do all the processing around that

Parameters:

  • title (String)

    The title the package belongs to

  • version (String, Xolo::Server::Version)

    the version string or object

  • pkg_src (String, Pathname)

    the path to the pkg



105
106
107
108
109
110
111
# File 'lib/xolo/server/helpers/file_transfers.rb', line 105

def process_and_upload_autopkg_pkg(title, version, pkg_src)
  process_and_upload_to_jamf(
    title,
    version,
    pkg_src: pkg_src
  )
end

#process_and_upload_to_jamf(title, version, pkg_src:, orig_filename: nil) ⇒ void

This method returns an undefined value.

Process a pkg installer and upload to jamf

Parameters:

  • title (String)

    the title name

  • version (String, Xolo::Server::Version)

    the version string or object

  • pkg_src (String, Pathname)

    the path to the file to be uploaded to Jamf This could be one uploaded from xadm, or one created by autopkg

  • orig_filename (String, nil) (defaults to: nil)

    the original filename of the pkg, e.g. as uploaded from an admin’s computer. If not provided, the basename of pkg_src will be used.



123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# File 'lib/xolo/server/helpers/file_transfers.rb', line 123

def process_and_upload_to_jamf(title, version, pkg_src:, orig_filename: nil)
  pkg_src = Pathname.new pkg_src
  orig_filename ||= pkg_src.basename.to_s

  version = instantiate_version(title: title, version: version) if version.is_a?(String)

  staged_pkg = prep_pkg_for_upload(version, pkg_src)
  upload_pkg_in_thread(version, staged_pkg, orig_filename)
rescue => e
  msg = "#{e.class}: #{e}"
  log_error msg
  e.backtrace.each { |line| log_error "..#{line}" }
  halt 400, { status: 400, error: msg }
ensure
  pkg_src.delete if pkg_src.file?
end

#process_and_upload_uploaded_pkgObject

Upload an pkg installer from xadm to Jamf Pro, and do all the processing around that



88
89
90
91
92
93
94
95
# File 'lib/xolo/server/helpers/file_transfers.rb', line 88

def process_and_upload_uploaded_pkg
  process_and_upload_to_jamf(
    params[:title],
    params[:version],
    pkg_src: params[:file][:tempfile].path,
    orig_filename: params[:file][:filename]
  )
end

#process_incoming_ssvc_iconObject

Store an uploaded self service icon in the title’s directory. It’ll be added to Policies and Patch Policies as needed (increasing the bloat in the database, of course)



67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/xolo/server/helpers/file_transfers.rb', line 67

def process_incoming_ssvc_icon
  filename = params[:file][:filename]
  tempfile = Pathname.new params[:file][:tempfile].path

  log_info "Processing uploaded SelfService icon for #{params[:title]}"
  title = instantiate_title params[:title]
  title.save_ssvc_icon(tempfile, filename)
  # this will configure the ssvc settings but won't make it available
  # in ssvc - that happens elsewhere.
  title.configure_pol_for_self_service
rescue => e
  msg = "#{e.class}: #{e}"
  log_error msg
  e.backtrace.each { |line| log_error "..#{line}" }

  halt 400, { status: 400, error: msg }
end

#process_incoming_testfileObject

upload a file for testing … anything



49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/xolo/server/helpers/file_transfers.rb', line 49

def process_incoming_testfile
  progress 'starting test file upload', log: :debug

  params[:file][:filename]
  tempfile = Pathname.new params[:file][:tempfile].path

  progress "1/3 TempFile is #{tempfile} size is #{tempfile.size}... is it still uploading?", log: :debug
  sleep 2
  progress "2/3 TempFile is #{tempfile} size is #{tempfile.size}... is it still uploading?", log: :debug
  sleep 2
  progress "3/3 TempFile is #{tempfile} size is #{tempfile.size}... is it still uploading?", log: :debug
  progress 'all done', log: :debug
end

#sign_and_stage(pkg_src, staged_pkg, version) ⇒ void

This method returns an undefined value.

If this pkg needs signing, do so putting the signed pkg in the staged_pkg location, and delete the original pkg_src file. If it doesn’t need signing, just move it to the staged_pkg location.

Parameters:

  • pkg_src (Pathname)

    the path to the file to be uploaded to Jamf

  • staged_pkg (Pathname)

    the path where the pkg should be staged for upload to Jamf

  • version (Xolo::Server::Version)

    the version that is being uploaded/re-uploaded



345
346
347
348
349
350
351
352
353
354
355
# File 'lib/xolo/server/helpers/file_transfers.rb', line 345

def sign_and_stage(pkg_src, staged_pkg, version)
  if need_to_sign?(pkg_src, version)
    # This will put the signed pkg into the staged_pkg location
    sign_pkg(pkg_src, staged_pkg)
    log_debug "Signing complete, signed pkg is '#{staged_pkg}', deleting original file '#{pkg_src}'"
    pkg_src.delete if pkg_src.file?
  else
    log_debug "The .pkg file doesn't need signing, moving pkg_src to '#{staged_pkg}'"
    pkg_src.rename staged_pkg
  end
end

#update_version_post_upload(version, staged_pkg, orig_filename) ⇒ void

This method returns an undefined value.

After uploading a pkg, update the version with info about the pkg, like whether it’s a dist pkg or not, and save the manifest and checksum NOTE this is run as part of the upload thread.

Parameters:

  • version (Xolo::Server::Version)

    the version that is being uploaded/re-uploaded

  • staged_pkg (Pathname)

    the path to the staged pkg that was uploaded to Jamf



284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
# File 'lib/xolo/server/helpers/file_transfers.rb', line 284

def update_version_post_upload(version, staged_pkg, orig_filename)
  # make note if the pkg is a Distribution package
  version.dist_pkg = pkg_is_distribution?(staged_pkg)

  # save the manifest on the server, just in case
  # TODO: Support sha3_512 in manifests
  version.manifest_file.pix_atomic_write(version.jamf_package.manifest)

  # save the checksum just in case
  version.sha_512 = version.jamf_package.checksum

  # don't save the admins local path to the pkg, just the filename they uploaded
  version.pkg_to_upload = orig_filename

  # save/update the local data file, since we've done stuff to update it
  version.save_local_data

  # log the upload
  version.log_change msg: "Uploaded pkg file '#{staged_pkg.basename}' to dist point"
ensure
  staged_pkg.delete if staged_pkg.file?
end

#upload_pkg_in_thread(version, staged_pkg, orig_filename) ⇒ void

This method returns an undefined value.

upload a prepped/staged pkg in a thread, and do the things that need to be done after upload, like setting the upload/reupload date and user, enabling the reinstall policy if it’s a reupload, etc

Parameters:

  • version (Xolo::Server::Version)

    the version that is being uploaded/re-uploaded

  • staged_pkg (Pathname)

    the path to the staged pkg that is ready to be uploaded to Jamf



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
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
# File 'lib/xolo/server/helpers/file_transfers.rb', line 177

def upload_pkg_in_thread(version, staged_pkg, orig_filename)
  if @pkg_upload_thread&.alive?
    msg = "A pkg upload is already in progress for version '#{version}' - can't start another one until it's done"
    log_error msg
    raise msg
  end

  @pkg_upload_thread = Thread.new do
    begin
      # is this a re-upload? The jamf_pkg_file will have already
      # been updated to reflect the new filename with the _N_ if it's a re-upload
      re_uploading = version.jamf_pkg_file =~ /_(\d+)_\.pkg$/

      # disable reinstall policy if re-uploading,
      # will be re-enabled after the upload
      if re_uploading
        action = REUPLOAD_ACTION
        pol = version.jamf_auto_reinstall_policy
        pol.disable
        pol.save
      else
        action = UPLOAD_ACTION
      end

      upload_to_dist_point(version.jamf_package, staged_pkg)

      uploaded_by =
        if version.title_object.autopkg_enabled?
          Xolo::Server::Helpers::AutoPkg::AUTOPKG_UPLOADED_BY
        elsif defined?(session)
          session[:admin]
        end

      if re_uploading
        version.reupload_date = Time.now
        version.reuploaded_by = uploaded_by

        # if upload via API, wait for pkg to appear then enable the policy
        if upload_via_api?
          wait_for_pkg_and_enable_reinstall_policy(version)

        # otherwise notify someone to confirm upload is complete before enabling the policy
        else
          msg = "Please confirm that re-uploaded pkg '#{version.jamf_pkg_file}' is on the dist point and ready to go, then enable the reinstall policy '#{pol.name}' at #{jamf_auto_reinstall_policy_url}"
          log_info msg, alert: true

        end # if upload_via_api?

        # update the dist filename in the jamf package object
        version.jamf_package.fileName = version.jamf_pkg_file
        version.jamf_package.packageName = version.jamf_pkg_name
        version.jamf_package.save

      # if this is a first-time upload, just set the upload date and user
      else
        version.upload_date = Time.now
        version.uploaded_by = uploaded_by
      end # if re_uploading
      version.save_local_data
      version.log_change msg: "#{action}ed pkg file '#{staged_pkg.basename}' to Jamf Pro dist point(s)"
    rescue => e
      msg = "Error in pkg upload thread: #{e.class}: #{e}"
      log_error msg
      e.backtrace.each { |line| log_error "..#{line}" }
    end # begin

    update_version_post_upload(version, staged_pkg, orig_filename)
  end # thread
end

#upload_to_dist_point(jpkg, pkg_file) ⇒ void

This method returns an undefined value.

upload a staged pkg to the dist point(s) This will also update the checksum and manifest.

Parameters:

  • jpkg (Jamf::JPackage)

    The package object for which the pkg is being uploaded

  • pkg_file (Pathname)

    The path to .pkg file being uploaded



442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
# File 'lib/xolo/server/helpers/file_transfers.rb', line 442

def upload_to_dist_point(jpkg, pkg_file)
  # via API
  if upload_via_api?
    log_debug 'Jamf: increasing the API timeout to 30 minutes for the pkg upload'
    jpkg.cnx.jp_cnx.options.timeout = 1800

    log_debug "Jamf: Attempting upload of #{pkg_file.basename} to primary dist point via API"
    # this will update the checksum and manifest automatically, and save back to the jamf pro server
    jpkg.upload pkg_file
    log_info "Jamf: Uploaded #{pkg_file.basename} to primary dist point via API, with new checksum and manifest"

  # via upload tool defined in config
  else
    log_debug "Jamf: Regenerating manifest for package '#{jpkg.packageName}' from #{pkg_file.basename}"
    jpkg.generate_manifest(pkg_file)

    log_debug "Jamf: Recalculating checksum for package '#{jpkg.packageName}' from #{pkg_file.basename}"
    jpkg.recalculate_checksum(pkg_file)

    log_info "Jamf: Saving package '#{jpkg.packageName}' with new checksum and manifest"
    jpkg.save
    upload_via_tool(jpkg, pkg_file)
  end
end

#upload_via_api?Boolean

are Dist Point uploads configured to be done via the API, or with an upload tool defined in config?

Returns:

  • (Boolean)


430
431
432
# File 'lib/xolo/server/helpers/file_transfers.rb', line 430

def upload_via_api?
  Xolo::Server.config.upload_tool.to_s.downcase == 'api'
end

#upload_via_tool(jpkg, pkg_file) ⇒ void

This method returns an undefined value.

upload the pkg with the uploader tool defined in config

Parameters:

  • version (Xolo::Server::Version)

    The version object

  • staged_pkg (Pathname)

    The path to the staged pkg



474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
# File 'lib/xolo/server/helpers/file_transfers.rb', line 474

def upload_via_tool(jpkg, pkg_file)
  log_info "Jamf: Uploading #{pkg_file.basename} to dist point(s) via upload tool"

  tool = Shellwords.escape Xolo::Server.config.upload_tool.to_s
  jpkg_name = Shellwords.escape jpkg.packageName
  pkg = Shellwords.escape pkg_file.to_s
  cmd = "#{tool} #{jpkg_name} #{pkg}"

  stdouterr, exit_status = Open3.capture2e(cmd)
  if exit_status.success?
    log_debug "Jamf: upload tool succeeded in uploading #{pkg_file.basename} to dist point(s)."
    return
  end

  msg = "Uploader tool failed to upload #{pkg_file.basename} to dist point(s): #{stdouterr}"
  log_error msg
  raise msg
end

#validate_uploaded_pkg(filename) ⇒ String

Confirm and return the extension of the originally uplaoded file, as .pkg

Parameters:

  • filename (String)

    The original name of the file uploaded to Xolo.

Returns:

  • (String)

    ‘.pkg’ is the only valid one for now



500
501
502
503
504
505
506
507
# File 'lib/xolo/server/helpers/file_transfers.rb', line 500

def validate_uploaded_pkg(filename)
  log_debug "Validating pkg file ext for '#{filename}'"

  file_extname = Pathname.new(filename).extname
  return file_extname if Xolo::OK_PKG_EXTS.include? file_extname

  raise "Bad filename '#{filename}'. Package files must end in #{Xolo::OK_PKG_EXTS.join(', or ')}"
end

#wait_for_pkg_and_enable_reinstall_policy(version) ⇒ void

This method returns an undefined value.

Wait for a re-uploaded pkg to appear on the Cloud DP after an API upload, then enable the reinstall policy

Parameters:



253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
# File 'lib/xolo/server/helpers/file_transfers.rb', line 253

def wait_for_pkg_and_enable_reinstall_policy(version)
  start_time = Time.now

  until cloud_dp_pkg_ready?(version.jamf_pkg_file)
    if Time.now - start_time > CLOUD_DP_UPLOAD_TIMEOUT
      msg = "Timed out waiting for pkg '#{version.jamf_pkg_file}' to appear on Cloud DP after upload via API"
      log_error msg
      raise msg
    end

    log_debug "Checking every minute for pkg '#{version.jamf_pkg_file}' to appear on Cloud DP after upload via API..."
    sleep 60
  end # until

  log_debug "Pkg '#{version.jamf_pkg_file}' is now on Cloud DP, enabling reinstall policy"
  pol = version.jamf_auto_reinstall_policy
  pol.enable
  pol.save

  msg = "Re-uploaded pkg '#{version.jamf_pkg_file}' is on the dist point and ready to go, reinstall policy '#{pol.name}' has been enabled"
  log_info msg, alert: true
end

#wrap_component_pkg_in_distribution(orig_pkg, version) ⇒ Pathname

Wrap a component pkg in a Distribution pkg, return the path to the Distribution pkg, which should be the same as the orig_pkg

Parameters:

  • orig_pkg (Pathname, String)

    The path to the component .pkg

  • version (Xolo::Server::Version)

    the version that is being uploaded/re-uploaded

Returns:

  • (Pathname)

    The path to the new Distribution pkg

Raises:

  • (ArgumentError)


380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
# File 'lib/xolo/server/helpers/file_transfers.rb', line 380

def wrap_component_pkg_in_distribution(orig_pkg, version)
  orig_pkg = Pathname.new(orig_pkg)

  raise ArgumentError, "pkg_file does not exist or not a file: #{orig_pkg}" unless orig_pkg.file?

  if pkg_is_distribution?(orig_pkg)
    log_debug "Package '#{orig_pkg.basename}' is already a Distribution pkg, not wrapping"
    return orig_pkg
  end

  log_info "Wrapping component pkg '#{orig_pkg.basename}' in a Distribution pkg"
  out_dir = orig_pkg.parent
  out_file = out_dir + "#{orig_pkg.basename(Xolo::DOT_PKG)}_dist#{Xolo::DOT_PKG}"

  # the productbuild command, with signing if needed
  prodbuild_cmd = +"/usr/bin/productbuild --package #{orig_pkg.to_s.shellescape} "
  signing_reason =
    if version.pkg_is_from_autopkg && Xolo::Server.config.sign_autopkg_pkgs
      'autopkg'
    elsif !version.pkg_is_from_autopkg && Xolo::Server.config.sign_pkgs
      'uploaded'
    end

  if signing_reason
    log_info "Signing is enabled for #{signing_reason} pkgs, will sign the Distribution pkg as part of wrapping process"

    sh_kch = Shellwords.escape Xolo::Server::Configuration::PKG_SIGNING_KEYCHAIN.to_s
    sh_ident = Shellwords.escape Xolo::Server.config.pkg_signing_identity
    unlock_signing_keychain
    prodbuild_cmd << "--sign #{sh_ident} --keychain #{sh_kch} "
  end

  prodbuild_cmd << out_file.to_s.shellescape

  log_debug "Wrapping component pkg in Distribution pkg with this command: #{prodbuild_cmd}"

  if system prodbuild_cmd
    # remove the component pkg
    orig_pkg.delete
    # rename the dist pkg to the original pkg name,
    out_file.rename orig_pkg

    return orig_pkg
  end

  raise "Failed to wrap component pkg '#{orig_pkg.basename}' in a Distribution pkg"
end