Please review pull request #245: Build a Rake task for building Apple Packages opened by (glarizza)

Description:

The goal is to have our release managers build all Puppet packages
within Rake, and so this commit adds a rake task (apple_package) that
will build a DMG-encapsulated package for OS X and put it into the
pkg/apple directory off of the Puppet Root.

To accomplish this goal, a folder structure is created in
/tmp/puppet/puppet-#{version} that mirrors the structure needed for
Apple's Packagemaker CLI tool to build a package for OS X. Next, the
necessary files are copied from the Puppet source into the structure in
/tmp/puppet/puppet-#{version} and then packagemaker is run
(targeting that folder structure) to build an initial package. Hdiutil
then encapsulates that package into a DMG, and the file is finally
copied into the pkg/apple directory.

  • Opened: Mon Dec 05 22:38:07 UTC 2011
  • Based on: puppetlabs:master (eb0db02f463df166fd7a8d14d126ab98364f3c66)
  • Requested merge: glarizza:feature/master/Rakefile_DMG (2ef68f67259cada1117c21996370709309ad5edd)

Diff follows:

diff --git a/tasks/rake/apple.rake b/tasks/rake/apple.rake
new file mode 100644
index 0000000..4bf0ca3
--- /dev/null
+++ b/tasks/rake/apple.rake
@@ -0,0 +1,164 @@
+# Title:        Rake task to build Apple packages for Puppet.
+# Author:       Gary Larizza
+# Date:         12/5/2011
+# Description:  This task will create a DMG-encapsulated package that will
+#               install Puppet on OS X systems. This happens by building
+#               a directory tree of files that will then be fed to the
+#               packagemaker binary (can be installed by installing the
+#               XCode Tools) which will create the .pkg file.
+#
+require 'fileutils'
+require 'erb'
+require 'find'
+require 'pathname'
+
+# Path to Binaries (Constants)
+TAR           = '/usr/bin/tar'
+CP            = '/bin/cp'
+INSTALL       = '/usr/bin/install'
+DITTO         = '/usr/bin/ditto'
+PACKAGEMAKER  = '/Developer/usr/bin/packagemaker'
+SED           = '/usr/bin/sed'
+
+# Class Instance Variables
+@title                 = "puppet-#{Puppet::PUPPETVERSION}"
+@reverse_domain        = 'com.puppetlabs.puppet'
+@package_major_version = Puppet::PUPPETVERSION.split('.')[0]
+@package_minor_version = Puppet::PUPPETVERSION.split('.')[1] +
+                         Puppet::PUPPETVERSION.split('.')[2]
+
+# Template-specific Variables
+@pm_restart            = 'None'
+@build_date            = Time.new.strftime("%Y-%m-%dT%H:%M:%SZ")
+
+# method:       make_directory_tree
+# description:  This method sets up the directory structure that packagemaker
+#               needs to build a package. A prototype.plist file (holding
+#               package-specific options) is built from an ERB template located
+#               in the tasks/rake/templates directory.
+def make_directory_tree
+  puppet_tmp      = '/tmp/puppet'
+  @scratch       = "#{puppet_tmp}/#{@title}"
+  @working_tree  = {
+     'scripts'   => "#{@scratch}/scripts",
+     'resources' => "#{@scratch}/resources",
+     'working'   => "#{@scratch}/root",
+     'payload'   => "#{@scratch}/payload",
+  }
+  puts "Cleaning Tree: #{puppet_tmp}"
+  FileUtils.rm_rf(puppet_tmp)
+  @working_tree.each do |key,val|
+    puts "Creating: #{val}"
+    FileUtils.mkdir_p(val)
+  end
+  File.open("#{@scratch}/#{'prototype.plist'}", "w+") do |f|
+    f.write(ERB.new(File.read('tasks/rake/templates/prototype.plist.erb')).result())
+  end
+end
+
+# method:        build_dmg
+# description:   This method builds a package from the directory structure in
+#                /tmp/puppet and puts it in the
+#                /tmp/puppet/puppet-#{version}/payload directory. A DMG is
+#                created, using hdiutil, based on the contents of the
+#                /tmp/puppet/puppet-#{version}/payload directory. The resultant
+#                DMG is placed in the pkg/apple directory.
+#
+def build_dmg
+  # Local Variables
+  dmg_format_code   = 'UDZO'
+  zlib_level        = '9'
+  dmg_format_option = "-imagekey zlib-level=#{zlib_level}"
+  dmg_format        = "#{dmg_format_code} #{dmg_format_option}"
+  dmg_file          = "#{@title}.dmg"
+  package_file      = "#{@title}.pkg"
+  pm_extra_args     = '--verbose --no-recommend --no-relocate'
+  package_target_os = '10.4'
+
+  # Build .pkg file
+  system("sudo #{PACKAGEMAKER} --root #{@working_tree['working']} \
+    --id #{@reverse_domain} \
+    --filter DS_Store \
+    --target #{package_target_os} \
+    --title #{@title} \
+    --info #{@scratch}/prototype.plist \
+    --scripts #{@working_tree['scripts']} \
+    --resources #{@working_tree['resources']} \
+    --version #{Puppet::PUPPETVERSION} \
+    #{pm_extra_args} --out #{@working_tree['payload']}/#{package_file}")
+
+  # Build .dmg file
+  system("sudo hdiutil create -volname #{@title} \
+    -srcfolder #{@working_tree['payload']} \
+    -uid 99 \
+    -gid 99 \
+    -ov \
+    -format #{dmg_format} \
+    #{dmg_file}")
+
+  if File.directory?("#{Pathname.pwd}/pkg/apple")
+    FileUtils.mv("#{Pathname.pwd}/#{dmg_file}", "#{Pathname.pwd}/pkg/apple/#{dmg_file}")
+  else
+    FileUtils.mkdir_p("#{Pathname.pwd}/pkg/apple")
+    FileUtils.mv(dmg_file, "#{Pathname.pwd}/pkg/apple/#{dmg_file}")
+  end
+end
+
+# method:        pack_puppet_source
+# description:   This method copies the puppet source into a directory
+#                structure in /tmp/puppet/puppet-#{version}/root mirroring the
+#                structure on the target system for which the package will be
+#                installed. Anything installed into /tmp/puppet/root will be
+#                installed as the package's payload.
+#
+def pack_puppet_source
+  work          = "#{@working_tree['working']}"
+  puppet_source = Pathname.pwd
+
+  # Make all necessary directories
+  directories = ["#{work}/private/etc/puppet/",
+                 "#{work}/usr/bin",
+                 "#{work}/usr/sbin",
+                 "#{work}/usr/share/doc/puppet",
+                 "#{work}/usr/share/man/man5",
+                 "#{work}/usr/share/man/man8",
+                 "#{work}/Library/Ruby/Site/1.8/puppet"]
+  FileUtils.mkdir_p(directories)
+
+  # Install necessary files
+  system("#{INSTALL} -o root -g wheel -m 644 #{puppet_source}/conf/auth.conf #{work}/private/etc/puppet/auth.conf")
+  system("#{DITTO} #{puppet_source}/bin/ #{work}/usr/bin")
+  system("#{DITTO} #{puppet_source}/sbin/ #{work}/usr/sbin")
+  system("#{INSTALL} -o root -g wheel -m 644 #{puppet_source}/man/man5/puppet.conf.5 #{work}/usr/share/man/man5/")
+  system("#{DITTO} #{puppet_source}/man/man8/ #{work}/usr/share/man/man8")
+  system("#{DITTO} #{puppet_source}/lib/ #{work}/Library/Ruby/Site/1.8/")
+
+  # Setup a preflight script and replace variables in the files with
+  # the correct paths.
+  system("#{INSTALL} -o root -g wheel -m 644 #{puppet_source}/conf/osx/preflight #{@working_tree['scripts']}")
+  system("#{SED} -i '' \"s\#{SITELIBDIR}\#/usr/lib/ruby/site_ruby/1.8\#g\" #{@working_tree['scripts']}/preflight")
+  system("#{SED} -i '' \"s\#{BINDIR}\#/usr/bin\#g\" #{@working_tree['scripts']}/preflight")
+
+  # Install documentation (matching for files with capital letters)
+  Dir.foreach("#{puppet_source}") do |file|
+    system("#{INSTALL} -o root -g wheel -m 644 #{puppet_source}/#{file} #{work}/usr/share/doc/puppet") if file =~ /^[A-Z][A-Z]/
+  end
+
+  # Set Permissions
+  executable_directories = [ "#{work}/usr/bin",
+                             "#{work}/usr/sbin",
+                             "#{work}/usr/share/man/man8"]
+  FileUtils.chmod_R(0755, executable_directories)
+  FileUtils.chown_R('root', 'wheel', directories)
+  FileUtils.chmod_R(0644, "#{work}/Library/Ruby/Site/1.8/")
+  FileUtils.chown_R('root', 'wheel', "#{work}/Library/Ruby/Site/1.8/")
+  Find.find("#{work}/Library/Ruby/Site/1.8/") do |dir|
+    FileUtils.chmod(0755, dir) if File.directory?(dir)
+  end
+end
+
+task :apple_package do
+  make_directory_tree
+  pack_puppet_source
+  build_dmg
+end
diff --git a/tasks/rake/templates/prototype.plist.erb b/tasks/rake/templates/prototype.plist.erb
new file mode 100644
index 0000000..36bea53
--- /dev/null
+++ b/tasks/rake/templates/prototype.plist.erb
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>CFBundleIdentifier</key>
+	<string><%= @title %></string>
+	<key>CFBundleShortVersionString</key>
+	<string><%= Puppet::PUPPETVERSION %></string>
+	<key>IFMajorVersion</key>
+	<integer><%= @package_major_version %></integer>
+	<key>IFMinorVersion</key>
+	<integer><%= @package_minor_version %></integer>
+	<key>IFPkgBuildDate</key>
+	<date><%= @build_date %></date>
+	<key>IFPkgFlagAllowBackRev</key>
+	<false/>
+	<key>IFPkgFlagAuthorizationAction</key>
+	<string>RootAuthorization</string>
+	<key>IFPkgFlagDefaultLocation</key>
+	<string>/</string>
+	<key>IFPkgFlagFollowLinks</key>
+	<true/>
+	<key>IFPkgFlagInstallFat</key>
+	<false/>
+	<key>IFPkgFlagIsRequired</key>
+	<false/>
+	<key>IFPkgFlagOverwritePermissions</key>
+	<false/>
+	<key>IFPkgFlagRelocatable</key>
+	<false/>
+	<key>IFPkgFlagRestartAction</key>
+	<string><%= @pm_restart %></string>
+	<key>IFPkgFlagRootVolumeOnly</key>
+	<true/>
+	<key>IFPkgFlagUpdateInstalledLanguages</key>
+	<false/>
+</dict>
+</plist>

    

--
You received this message because you are subscribed to the Google Groups "Puppet Developers" group.
To post to this group, send email to puppet-dev@googlegroups.com.
To unsubscribe from this group, send email to puppet-dev+unsubscr...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/puppet-dev?hl=en.

Reply via email to