Sean Kaiser (dot) com

A home for things (work-related & not) that I feel inclined to share with others.

AutoPkg Change Notifications

| Comments

Background

We use AutoPkg to automatically download (and process into munki) updates to our commonly installed applications and internet plugins. One common practice is to run AutoPkg via JenkinsCI, but I have not taken the time to install and configure Jenkins, so I just run AutoPkg via a cron launch daemon script.

Because I didn’t want to have to check munki periodically to see if AutoPkg had downloaded anything, I wrote a wrapper script which emailed me the output from each AutoPkg run, which in our environment happens at the top of each hour. After a weekend of getting hourly emails (which I had to browse to check for any updates), I decided there had to be a better way to be notified of AutoPkg’s work.

Behold the power of diff

As I started thinking about my approach to this problem, I immediately thought that I wanted to be notified only when something was different from a ‘nothing downloaded, packaged or imported’ (the actual output from AutoPkg when nothing has been done) run. And how better to do that than to compare the output from the run against the output when ‘nothing (was) downloaded, packaged or imported.’

My solution was to save the output of a AutoPkg run when nothing was done, and on each subsequent AutoPkg run, check that run’s output against the saved output. If there are differences between these two files (because of either an update being downloaded, packaged or imported, or a download error), the script emails me the current AutoPkg run’s output. This way when I get the email from the script, I know there’s something that likely needs my attention.

Customizing the script

I’ve modified the script somewhat from my initial version, making it easier for other admins to customize for their environment. As you can see, there are three customization variables (recipe_list, mail_recipient, and autopkg_user) in the script. The only changes you’ll need to make to the script are in those three lines.

recipe_list

You’ll want to add whatever recipes you’re feeding to AutoPkg on this line. If you’re using munki, you’ll want to be sure to include MakeCatalogs.munki at the end of the list, of course. In the example, I’m only using the AdobeFlashPlayer.munki and MakeCatalogs.munki recipes, indicating that I want to download and import any Flash updates, then rebuild the munki catalogs.

mail_recipient

This is the email address you want the change notification to be sent to.

autopkg_user

This is the local user that will be running the autopkg-wrapper script. The default (nothing changed) log will be stored in this user’s Documents/autopkg folder.

Installation and initial configuration

Download the script and install it in /usr/local/bin.

As you’re setting things up, you’ll want to run this script manually, and you’ll be prompted to run the script with the initialize argument, which will tell the script to run autopkg with your recipe list twice, the second of which will save the output information to the default log location for later reference. You should also manually run the script with the initialize option if you change the recipe list, since that will change the output.

Once you’ve run the script manually, you’re ready to set the script to run autoatically, either via cron or launchd. Obviously Apple wants you to run things via launchd, but it will work just fine as a cron job. An example launchd plist file is available in my github repository. You’ll need to modify the plist to meet your needs. It assumes the following:

  • the autopkg-wrapper.sh script lives in /usr/local/bin,
  • the user who runs the script is autopkg,
  • you want the script to run hourly, at the top of the hour.

If you’re using the launchd plist, be sure to load it via something like launchctl load /Library/LaunchDaemons/com.example.autopkg-wrapper.plist (or reboot your machine, which will force the plist to be loaded on boot.)

What else?

That’s pretty much it. Modify the three variables, load the launchd plist (or add to cron), and wait for the email notifications when AutoPkg finds and processes updates for you.

The code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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
#!/bin/sh

# autopkg automation script which, when run with no arguments, checks current run's output against a default output and sends the output to a user if there are differences

# adjust the following variables for your particular configuration
recipe_list="AdobeFlashPlayer.munki MakeCatalogs.munki"
mail_recipient="you@yourdomain.net"
autopkg_user="autopkg"

# don't change anything below this line

# define logger behavior
logger="/usr/bin/logger -t autopkg-wrapper"
user_home_dir=`dscl . -read /Users/${autopkg_user} NFSHomeDirectory | awk '{ print $2 }'`

# run autopkg
if [ "${1}" == "help" ]; then
  # show some help with regards to initialization option
  echo "usage: ${0} [initialize]"
  echo "(initializes a new default log for notification checking)"
  exit 0

elif [ "${1}" == "initialize" ]; then
  # initialize default log for automated run to check against for notification if things have changed
  $logger "starting autopkg to initialize a new default output log"

  echo "recipe list: ${recipe_list}"
  echo "autopkg user: ${autopkg_user}"
  echo "user home dir: ${user_home_dir}"

  # make sure autopkg folder exists in autopkg_user's Documents folder
  if [ ! -d "${user_home_dir}"/Documents/autopkg ]; then
    /bin/mkdir -p "${user_home_dir}"/Documents/autopkg
  fi

  # run autopkg twice, once to get any updates and the second to get a log indicating nothing changed
  $logger "autopkg initial run to temporary log location"
  echo "for this autopkg run, output will be shown"
  /usr/local/bin/autopkg run -v ${recipe_list} 2>&1

  $logger "autopkg initial run to saved log location"
  echo "for this autopkg run, output will not be shown, but rather saved to default log location (${user_home_dir}/Documents/autopkg/autopkg.out"
  /usr/local/bin/autopkg run ${recipe_list} 2>&1 > "${user_home_dir}"/Documents/autopkg/autopkg.out

  $logger "finished autopkg"

elif [ ! -f "${user_home_dir}"/Documents/autopkg/autopkg.out ]; then
  # default log doesn't exist, so tell user to run this script in initialization mode and exit
  echo "ERROR: default log does not exist, please run this script with initialize argument to initialize the log"
  exit -1

else
  # default is to just run autopkg and email log if something changed from normal
  $logger "starting autopkg"
  /usr/local/bin/autopkg run ${recipe_list} 2>&1 > /tmp/autopkg.out

  $logger "finished autopkg"

  # check output against the saved log and if differences exist, send current log to specified recipient
  if [ "`diff /tmp/autopkg.out \"${user_home_dir}\"/Documents/autopkg/autopkg.out`" != "" ]; then
    # there are differences from a "Nothing downloaded, packaged or imported" run... might be an update or an error
    $logger "sending autopkg log"
    /usr/bin/mail -s "autopkg log" ${mail_recipient}  < /tmp/autopkg.out
    $logger "sent autopkg log to {$mail_recipient}, `wc -l /tmp/autopkg.out | awk '{ print $1 }'` lines in log"
  else
    $logger "autopkg did nothing, so not sending log"
  fi
fi
exit 0

Comments