Script AutoPkg trust verification and trust update process

Starting with version 1, AutoPkg began evaluating trust info for recipes, so you could see what changes were made to a recipe (if changes were made) and then accept the changes if you wanted to. Here is what the typical trust verification workflow looks like.

Whether running a list of recipes via script or via AutoPkgr schedule, I'd occasionally get error'ed recipes when trust was broken, have to manually run

autopkg verify-trust-info -vv NAMEOFRECIPE
and then, after review, run
autopkg update-trust-info NAMEOFRECIPE
and then run the recipe after updating the trust info:
autopkg run -v NAMEOFRECIPE
So I thought I'd take a stab at scripting the whole process. Basically my script updates all the repos (to see if there are changes), verifies trust info on each of the recipes in the recipe list, and then prompts the user to review changes and approve them or not, before running all the approved or unchanged recipes.

It's still in the early testing phase, but it seems to work so far....

How to read from and write to .plist files using Python

Python and Macs

Python is a popular tool among Mac Admins. You can find a fairly comprehensive (and long) list of Python-based Mac Admin tools at Python Macadmin Tools, so it's a handy thing to be able to throw together a Python script every now and then.

.plist files

A lot of settings in Mac OS X are managed in property lists (or .plist files), so it's also a handy thing to be able to use a Python script to manipulate .plist files.

.plist files and Bash (Bourne Again Shell)

Generally, if you're using bash (the default shell when you open the Terminal.app), you would read from and write to .plist files using a defaults command. For example, if you wanted to see whether the last user logged in (loggedIn) is still logged in or if no one is logged in (loggedOut or Restart), you would use a command like this:

defaults read /Library/Preferences/com.apple.loginwindow lastUser
or if you wanted to show hidden files, you would change the relevant .plist using a command like this:
defaults write com.apple.Finder AppleShowAllFiles -bool TRUE

.plist files and Python... and plistlib

This is where things get a bit tricky, because there is no equivalent to defaults in Python. Python has a module you can import called plistlib that presumably lets you read from and write to .plist files.

If you follow the examples in the documentation, though, you may run into some errors.

For example, if you paste in the code on how to generate a .plist (even if you import datetime, plistlib, and time), you'll get an error of

NameError: name 'fileName' is not defined
and then if you actually define fileName with the path to a file, you'll get
NameError: name 'dump' is not defined
Now I get that probably dump needs to be imported from some other module, but seriously in documentation you need to have code that people can copy and paste and see the results of and tweak, instead of having code that's basically useless.

That's why I'm writing this guide, because it's difficult to find straightforward documentation on how to actually use plistlib.

This is an actual basic script that will actually write a .plist based on a dictionary you define:

#!/usr/bin/python

import os
import plistlib

def main():
   pl = {
   "aString" : "Doodah",
   "aList" : ["A", "B", 12, 32.1, [1, 2, 3]],
   "aFloat" : 0.1,
   "anInt" : 730
   }

   fileName=os.path.expanduser('~/Desktop/example.plist')

   plistlib.writePlist(pl, fileName)

if __name__ == '__main__':
   main()
and if you want to read a .plist, this is actual real code that will really work (obviously the script above and script below are just examples, and you would tweak them to fit your workflow):
#!/usr/bin/python

import os
import plistlib

def main():

   fileName=os.path.expanduser('~/Desktop/example.plist')
   
   if os.path.exists(fileName):

      pl=plistlib.readPlist(fileName)
   
      print '\nThe plist full contents is %s\n' % pl

      if 'aString' in pl:
         print 'The aString value is %s\n' % pl['aString']
      else:
         print 'There is no aString in the plist\n'

   else:
      print '%s does not exist, so can\'t be read' % fileName

if __name__ == '__main__':
   main()
You may run into an issue, though, with some .plist files, and you'll get an error message like this:
Traceback (most recent call last):
File "./NAMEOFYOURSCRIPT.py", line 26, in
main()
File "./NAMEOFYOURSCRIPT.py", line 13, in main
pl=plistlib.readPlist(fileName)
File
"/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/plistlib.py", line 78, in readPlist
rootObject = p.parse(pathOrFile)
File
"/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/plistlib.py", line 406, in parse
parser.ParseFile(fileobj)
xml.parsers.expat.ExpatError: not well-formed (invalid token): line 1, column 8
I believe this stems from some .plist files being XML and others being binary. You can revert to bash and plutil to convert from binary to XML, but extra-converting a file in bash to be able to read from it in Python isn't ideal.

.plist files and Python... and FoundationPlist

Fortunately, there's a solution for this. FoundationPlist (by Greg Neagle, author of Munki) can read from and write to both XML and binary .plist files reliably.

To use FoundationPlist, just put it and its corresponding __init__.py in a FoundationPlist subfolder of your Python script, and then put

import FoundationPlist
in your Python script.

You can see a good example of this subfolder setup in Outset's code.

Once the FoundationPlist module is imported, you can use it similarly to how you would use plistlib:

munki_prefs_location='/Library/Preferences/ManagedInstalls.plist'
munki_prefs=FoundationPlist.readPlist(munki_prefs_location)
manifest=munki_prefs['ClientIdentifier']
print 'The client identifier is %s' % manifest
This is by no means a comprehensive guide to manipulating .plist files using Python—it's just a start, because I couldn't find good, usable documentation on the basics (read a .plist, write to a .plist).

PlistBuddy (not Python)

If you don't need to use Python and the defaults command isn't cutting it for you, Macs also come with a handy built-in command-line tool called PlistBuddy that can manipulate both binary and XML .plist files. I have several tutorials on how to use PlistBuddy.

Script to copy a Munki generic icon

Munki is fairly good at extracting icons from applications. Sometimes, though, you have items in your Munki catalog that aren't exactly easily icon-able, so you may want to attach a generic icon to it instead.

I wrote a Python script that will copy a generic icon to any of the Munki repository items missing an icon: MunkiGenericIcons.

Avoiding a .csv error with PrinterGenerator

PrinterGenerator is one of the automated scripts for adding printers to Munki.

It's pretty cool—you just put together a .csv of your printers and their IP addresses, and then run the script on the .csv, and all the pkginfo files for the printers will generate.

I did run into one snag, though, which is using Office on a Mac to generate the .csv. I got this error:

_csv.Error: new-line character seen in unquoted field - do you need to open the file in universal-newline mode?

I found out from Stack Overflow that, apparently, saving as a .csv may generate that Python error, but if you save as a Windows Comma Separated (.csv), it works just fine.

Getting started with pygame on a Mac

There are a lot of pygame tutorials out there, but I haven't yet found a simple, step-by-step how-to on how to just get pygame installed on a Mac, and then actually use it. So, hopefully, this will work for you. This example was done using Macs running OS X 10.10 (Yosemite). Your mileage may vary.

Installing pygame

Go to the pygame downloads page and scroll down to the Mac section.

Find the download titled Lion apple supplied python: pygame-1.9.2pre-py2.7-macosx10.7.mpkg.zip and download and install it.

It says Lion, but it will work with Yosemite.

Creating a short sample pygame

Just so you can see how it works basically (and then later on, you can create/tweak your own games), here's one you can start with.

Open up a text editor (e.g., a terminal editor like nano or a graphical one like TextWrangler—avoid TextEdit, unless you know the difference between plain text and rich text).

Paste into the text editor the following:

import sys, pygame
pygame.init()

size = width, height = 320, 240
speed = [2, 2]
black = 0, 0, 0

screen = pygame.display.set_mode(size)

ball = pygame.image.load("ball.gif")
ballrect = ball.get_rect()

while 1:
        for event in pygame.event.get():
                if event.type == pygame.QUIT: sys.exit()

        ballrect = ballrect.move(speed)
        if ballrect.left < 0 or ballrect.right > width:
                speed[0] = -speed[0]
        if ballrect.top < 0 or ballrect.bottom > height:
                speed[1] = -speed[1]

        screen.fill(black)
        screen.blit(ball, ballrect)
        pygame.display.flip()
Save it to your desktop as pygametest.py

Then, save this beach ball image file to your desktop as well.

In Terminal.app (which you can find in /Applications/Utilities or using Spotlight), paste in this command:

cd ~/Desktop
This will change focus to your desktop directory (that's where you saved your Python script and your beach ball image).

Paste this command in next to run your script:

python pygametest.py

pygamebasics
If it worked, you should see a beach ball bouncing around a black background.

Credit where credit's due

I didn't make up this tutorial out of thin air. This is a synthesis of a couple of online resources I found.