Adding macOS Preferences to ANTS

About a year ago, I made one of my first major open-source contributions to the MacAdmin community: adding the ability to configure the ANTS Ansible-pull framework using native macOS preferences using PyObjC.

Background

Ansible is one of several popular configuration management tools, and the one I am most familiar with. Unlike scripting, in which a series of instructions written and run on a client, configuration management tools are based around the idea of describing a “state”—these packages are installed, this file has these permissions, that service is running, etc.—and then bringing the client machine in compliance with that description. So rather than describing things to do, you instead describe things that are, and leave the “how” to your config management tool of choice. Ansible is the simplest of the three config management tools I’ve used (the others being Puppet and Chef), both syntactically and in terms of necessary infrastructure, but they all have their own strengths and weaknesses.

By default, Ansible follows an agentless “push” model: running on a central server, it pushes configuration changes over SSH. While this model generally works well with static servers, it is less useful with user endpoints, which may not have a static IP or are behind a NAT. For these instances, however, Ansible has a “pull” mode where runs locally and checks out configuration from a central location. To make this easier to use, the Client Services Team of the University of Basel IT Services has released a wrapper for Ansible-Pull it calls the ANTS Framework, or simply ANTS. ANTS is designed to configure macOS and Linux clients and interface with directory services such as Active Directory. (You can learn more about ANTS and how it works here and here.)

Setting the Foundation

ANTS is configured using a config file that stores settings, such as the git repository to check out, how often it runs, and Active Directory information to allow it to connect and interact with Active Directory. These settings can be configured via the command line tool or by modifying or replacing the file directly. However, macOS has its own native preference format stored in specially-formatted XML files called plists and modified using Apple’s defaults tool. These settings can also be controlled using configuration profiles (.mobileconfig files) which are the primary method of managing macOS settings and can be deployed via MDM or other tools such as Munki.

Implementing CFPreferencesCopyAppValue

Like Ansible, ANTS is written in Python and so can integrate PyObjC, a bridge between Python and Objective-C. PyObjC’s Foundation framework allows programs written in pure Python to interact directly with native macOS preferences. While Foundation provides an API for reading and writing both system and user preferences, ANTS only requires the CFPreferencesCopyAppValue method for reading system (root) preference values.

Use of CFPreferencesCopyAppValue in macos_prefs.py in the ANTS framework.

With Foundation and the CFPreferencesCopyAppValue method (and a quick conversion from Objective-C types to their Python equivalents) we can easily create a key-value dictionary of ANTS preferences identical to those read from the standard config file—the only difference being from those managed through native macOS methods. From there it is a simple matter of merging the two dictionaries

In order to minimize changes to the core ANTS code base, the code dealing with macOS is placed in a separate module and imported and implemented in the main configer.py module as unobtrusively as possible. If the import statement fails, as it will if the Foundation library is not present, then none of the macOS-specific code is ever used. If successful, any macOS preference values will override those set in the ANTS config file. This behavior allows administrators to continue to use the default values and only set the values they wish to using a configuration profile or another native macOS method.

Code in ANTS configer.py dedicated to importing and handling the macos_prefs module.

Conclusion

While not the most sophisticated or impressive open source contribution, it is one I am proud of. With a relatively small and unobtrusive change to the ANTS code base, I made is significantly more useful to myself and hopefully other administrators. This addition to ANTS opens up the prospect of using macOS-specific tools and workflows to better manage the ANTS framework. Configuration profiles can be laid down by an MDM during a DEP first-boot workflow even before ANTS itself is installed, making it a powerful tool for bootstrapping and configuring new machines.

I have since gone on to use the Foundation framework and it’s macOS preference utilities in other projects, and would encourage other developers working on Python tools for macOS to do the same.