Comparing a JamfUploader recipe to a standard JSSImporter recipe
I have now deprecated the AutoPkg processor JSSImporter, and it will likely stop working sometime between August and December of this year, 2022 - that is, unless somebody volunteers to take it over and is able to adapt it to work with Jamf’s upcoming requirement for token-based API authentication. Once this change happens, you will no longer be able to use any .jss
recipes.
JamfUploader is a new set of AutoPkg processors that I designed to replace JSSImporter, and it has the same functionality as JSSImporter (and much more!), but it cannot read .jss
recipes. This is already working with token-based auth.
JSSImporter has many options, so .jss
recipes can come in many forms. However, a “standard” of sorts was defined, and “standard” .jss
recipes were offered in the autopkg/jss-recipes repository.
The purpose of this blog post is to describe the “standard” JSS recipe and compare it in detail to a .jamf
recipe that uses JamfUploader processors .I hope this will help to show how to construct a .jamf
recipe that will do exactly the same things as the .jss
recipe you have been using until now.
A standard JSS recipe
Standard JSS recipes were designed to perform the following, where Firefox
may be substituted by Google Chrome
, TeamViewer
or whatever:
- Create a category for the package if it doesn’t already exist.
- Upload a package, either to a Jamf Cloud or other cloud distribution point, or to an on-premises fileshare distribution point, and assign a category and other metadata to the package object in Jamf Pro .
-
Create or update a smart group named
Firefox-update-smart
, which contains the following criteria:Application Title: is: Firefox.app and Application Version: is not: X.Y.Z and Computer Group member of: Testing
- Create a category for the policy if it doesn’t already exist.
- Create or update a Self Service policy named
Install Latest Firefox
, which includes the uploaded package, is scoped to theFirefox-update-smart
smart group, and is assigned a category and an icon (the icon is uploaded).
Some applications require additional items to work properly such as a script and/or an extension attribute, but here we will assume the application doesn’t need these.
To achieve this, each recipe requires access to additional files:
- A smart group template file in XML form.
- A policy template file in XML form.
- A saved copy of the app icon as a PNG.
These should be stored in the same directory as the recipe, or copied into the RecipeOverrides folder.
Let’s look at each of the files:
Firefox.jss.recipe
Here is the .jss
recipe in both yaml
and plist
formats:
Notice that although there are several API actions required to achieve the four steps above, those actions are all bundled into one process. Keep that in mind as we look at a .jamf
recipe later.
The recipe requires these Input keys:
NAME: Firefox
CATEGORY: Productivity
GROUP_NAME: "%NAME%-update-smart"
GROUP_TEMPLATE: SmartGroupTemplate.xml
POLICY_CATEGORY: Testing
POLICY_TEMPLATE: PolicyTemplate.xml
SELF_SERVICE_DESCRIPTION: Mozilla Firefox is a free and open source web browser.
SELF_SERVICE_ICON: Firefox.png
SmartGroupTemplate.xml
The standard SmartGroupTemplate looks like this:
Here, notice some more keys that are not provided in the Input array of the recipe:
group_name
- this is interpreted by the JSSImporter processor from theGROUP_NAME
key above. I’m not sure whyGROUP_NAME
is not used in the template.JSS_INVENTORY_NAME
- this is a key that is interpreted by the JSSImporter processor as%NAME%.app
, but can optionally be supplied in the recipe.VERSION
- this is interpreted by the JSSImporter processor from theversion
key supplied by a parent recipe. I’m not sure whyversion
is not used.
PolicyTemplate.xml
Here’s the policy template:
Again, we have some keys that are not supplied in the recipe but interpreted by the JSSImporter processor:
PROD_NAME
- this is interpreted asNAME
but can be supplied in the recipe. I never really understood what this special key was for.VERSION
- see above.
Additionally, the scope, package and scripts sections are omitted, worked out automatically by JSSImporter. This is great for “compliant” policies but is a bit of a black box for more complex setups, and had bugs. For example, JSSImporrter recipes never were able to supply both parameter labels to scripts and parameter values to policies.
<scope>
<!--Scope added by JSSImporter-->
</scope>
<package_configuration>
<!--Package added by JSSImporter-->
</package_configuration>
<scripts>
<!--Scripts added by JSSImporter-->
</scripts>
A functionally equivalent Jamf recipe
Now let’s look at a functionally equivalent .jamf
recipe. JamfUploader recipes do not bundle all the different API actions into one process. Each API action gets its own processor. So, when we want to do all the same things that a “standard” .jss
recipe does, we will use the following processors:
JamfCategoryUploader
- Create a category for the package if it doesn’t already exist.JamfPackageUploader
- Upload a package, either to a Jamf Cloud or other cloud distribution point, or to an on-premises fileshare distribution point, and assign a category and other metadata to the package object in Jamf Pro.JamfComputerGroupUploader
- Create or update a smart group namedFirefox-update-smart
.JamfCategoryUploader
- Create a category for the policy if it doesn’t already exist.JamfPolicyUploader
- Create or update a Self Service policy namedInstall Latest Firefox
, which includes the uploaded package, is scoped to theFirefox-update-smart
smart group, and is assigned a category and an icon (the icon is uploaded).
Including the JamfCategoryUploader
processors is optional. If the categories already exist, and/or you want the fail if they don’t, then these don’t need to be in the recipe at all.
And if you don’t want to create a smart group or policy, but just upload the package, you can omit the JamfComputerGroupUploader
and JamfPolicyUploader
processors too - just use JamfPackageUploader
.
However, let’s look at a .jamf
recipe that functionally equivalent to a “standard” .jss
recipe.
Firefox.jamf.recipe.yaml
Here is the .jamf
recipe in both yaml
and plist
formats:
The Input array is not much changed from the .jss
recipe:
NAME: Firefox
CATEGORY: Productivity
GROUP_NAME: "%NAME%-update-smart"
GROUP_TEMPLATE: JamfSmartGroupTemplate.xml
TESTING_GROUP_NAME: Testing
POLICY_CATEGORY: Testing
POLICY_TEMPLATE: JamfPolicyTemplate.xml
POLICY_NAME: "Install Latest %NAME%"
SELF_SERVICE_DISPLAY_NAME: "Install Latest %NAME%"
SELF_SERVICE_DESCRIPTION: Mozilla Firefox is a free and open source web browser.
SELF_SERVICE_ICON: "%NAME%.png"
UPDATE_PREDICATE: "pkg_uploaded == False"
In this example, I have included extra keys for POLICY_NAME
and SELF_SERVICE_DISPLAY_NAME
so that these can be overridden (in contrast, those names are fixed in the .jss
recipe), and I’ve included the TESTING_GROUP_NAME
which is hard-coded as Testing
in the SmartGroupTemplate of the .jss
recipes, so that this can also be overridden.
The only other extra key is UPDATE_PREDICATE
. More on that below.
Now take a look at the processes in the recipe.
First, we are supplying a category name and asking JamfCategoryUploader
to create it if it doesn’t exist.
- Processor: com.github.grahampugh.jamf-upload.processors/JamfCategoryUploader
Arguments:
category_name: "%CATEGORY%"
Next, we supply the category name into the JamfPackageUploader
processor, and ask it to upload the package. It gets the pkg_name
and pkg_path
from the parent recipe, so we don’t need to supply those values here.
- Processor: com.github.grahampugh.jamf-upload.processors/JamfPackageUploader
Arguments:
category_name: "%CATEGORY%"
Next comes a StopProcessingIf
processor. This recipe includes a StopProcessingIf
process, to check that the package was uploaded or not. JamfPackageUploader
will not upload the package if the name is the same as an existing package. If the package is not uploaded, the recipe run will stop at StopProcessingIf
because the predicate pkg_uploaded == False
is matched. Including an Input key for UPDATE_PREDICATE
allows us to force the recipe to continue to update smart group and policy even if no new package was uploaded. This provides the equivalent workflow to the JSSImporter key STOP_IF_NO_JSS_UPLOAD
that users of JSS recipes will be familiar with.
- Processor: StopProcessingIf
Arguments:
predicate: "%UPDATE_PREDICATE%"
Next we supply the Smart Group Template’s filename and the name of the smart group. The template can be situated anywhere in the same repo as the recipe, or in the RecipeOverrides folder. If not in these places, a full filepath can be provided.
- Processor: com.github.grahampugh.jamf-upload.processors/JamfComputerGroupUploader
Arguments:
computergroup_template: "%GROUP_TEMPLATE%"
computergroup_name: "%GROUP_NAME%"
After another JamfCategoryUploader
process to create the policy’s category if absent, we supply the policy name, the policy template’s filename, and the icon’s filename.
- Processor: com.github.grahampugh.jamf-upload.processors/JamfPolicyUploader
Arguments:
policy_template: "%POLICY_TEMPLATE%"
policy_name: "%POLICY_NAME%"
icon: "%SELF_SERVICE_ICON%"
JamfSmartGroupTemplate.xml
The standard SmartGroupTemplate for a .jamf
recipe looks like this:
It is almost identical to the template for the .jss
recipe. The only differences are that there are no special interpretation of the group name or version strings, so we supply GROUP_NAME
and version
directly. Also, I have decided to allow the name of the “Testing” group to be overridden, so I use the key TESTING_GROUP_NAME
rather than hard-coding in the Testing
group name.
JamfPolicyTemplate.xml
Here’s the policy template for the .jamf
recipe:
In comparison to the template for the .jss
recipe, the main difference is that there is no “special” handling of the package_configuration
, scope
and scripts
sections. These are instead exactly as you will see if you download an existing policy from your Jamf server using the Classic API.
Furthermore, there is no special interpreted keys in the template. So there is no PROD_NAME
or VERSION
. Instead we supply POLICY_NAME
directly from the Input array, and version
comes directly from the parent recipe.
Conclusion
I hope this has given a good explanation of the small differences between the outgoing .jss
recipe format and the new .jamf
recipes.
If you want to see more examples, I have converted most of the recipes from the old jss-recipes repo into .jamf
format, available in my own recipe repo at grahampugh-recipes/Jamf_Recipes.
You can also easily convert any “standard” .jss
recipes from other repos, or ones that you wrote yourself, using the JamfRecipeMaker tool that I introduced in a previous blog post.