Closed sggr57a closed 6 years ago
Something wrong in here??
def handle_category(self, category_type): """Ensure a category is present.""" if self.env.get(category_type): category_name = self.env.get(category_type) try: category = self.jss.Category(category_name) self.output("Category type: %s-'%s' already exists " "according to JSS, moving on..." % (category_type, category_name)) except jss.JSSGetError:
category = jss.Category(self.jss, category_name)
category.save()
self.output(
"Category type: %s-'%s' created." % (category_type,
category_name))
self.env["jss_changed_objects"]["jss_category_added"].append(
category_name)
else:
category = None
return category
If you can post the entire verbose output of the JSSImporter section of the AutoPkg output, we could probably narrow it down.
Cool ok here's my output for one recipe and the accompanying JSSImporter code I'm using..
Processing AdobeFlashPlayer.jss... AdobeFlashURLProvider AdobeFlashURLProvider: Found URL http://fpdownload.macromedia.com/get/flashplayer/pdc/22.0.0.192/install_flash_player_osx.dmg URLDownloader URLDownloader: Item at URL is unchanged. URLDownloader: Using existing /var/root/Library/AutoPkg/Cache/com.github.jss-recipes.jss.AdobeFlashPlayer/downloads/AdobeFlashPlayer.dmg EndOfCheckPhase CodeSignatureVerifier CodeSignatureVerifier: Mounted disk image /var/root/Library/AutoPkg/Cache/com.github.jss-recipes.jss.AdobeFlashPlayer/downloads/AdobeFlashPlayer.dmg CodeSignatureVerifier: Verifying installer package signature... CodeSignatureVerifier: Package "Adobe Flash Player.pkg": CodeSignatureVerifier: Status: signed by a certificate trusted by Mac OS X CodeSignatureVerifier: Certificate Chain: CodeSignatureVerifier: 1. Developer ID Installer: Adobe Systems, Inc. CodeSignatureVerifier: SHA1 fingerprint: 9D 75 C9 20 01 4A 65 04 94 A7 63 95 E3 91 93 47 04 E8 57 DF CodeSignatureVerifier: ----------------------------------------------------------------------------- CodeSignatureVerifier: 2. Developer ID Certification Authority CodeSignatureVerifier: SHA1 fingerprint: 3B 16 6C 3B 7D C4 B7 51 C9 FE 2A FA B9 13 56 41 E3 88 E1 86 CodeSignatureVerifier: ----------------------------------------------------------------------------- CodeSignatureVerifier: 3. Apple Root CA CodeSignatureVerifier: SHA1 fingerprint: 61 1E 5B 66 2C 59 3A 08 FF 58 D1 4A E2 24 52 D1 98 DF 6C 60 CodeSignatureVerifier: CodeSignatureVerifier: Signature is valid CodeSignatureVerifier: Authority name chain is valid Versioner Versioner: Mounted disk image /var/root/Library/AutoPkg/Cache/com.github.jss-recipes.jss.AdobeFlashPlayer/downloads/AdobeFlashPlayer.dmg Versioner: Found version 22.0.0.192 in file /private/tmp/dmg.t1qZ0X/Install Adobe Flash Player.app/Contents/Info.plist PkgCopier PkgCopier: Mounted disk image /var/root/Library/AutoPkg/Cache/com.github.jss-recipes.jss.AdobeFlashPlayer/downloads/AdobeFlashPlayer.dmg PkgCopier: Copied /private/tmp/dmg.iRp3rY/Install Adobe Flash Player.app/Contents/Resources/Adobe Flash Player.pkg to /var/root/Library/AutoPkg/Cache/com.github.jss-recipes.jss.AdobeFlashPlayer/AdobeFlashPlayer-22.0.0.192.pkg JSSImporter JSSImporter: Category type: category-'Digital Media' already exists according to JSS, moving on... JSSImporter: Category type: policy_category-'Testing' already exists according to JSS, moving on... JSSImporter: Pkg-object already exists according to JSS, moving on... 'NoneType' object has no attribute 'exists' Failed. Receipt written to /var/root/Library/AutoPkg/Cache/com.github.jss-recipes.jss.AdobeFlashPlayer/receipts/AdobeFlashPlayer-receipt-20160707-105741.plist
The following recipes failed: AdobeFlashPlayer.jss Error in com.github.jss-recipes.jss.AdobeFlashPlayer: Processor: JSSImporter: Error: 'NoneType' object has no attribute 'exists'
The following packages were copied: Pkg Path
/var/root/Library/AutoPkg/Cache/com.github.jss-recipes.jss.AdobeFlashPlayer/AdobeFlashPlayer-22.0.0.192.pkg
######################################################################
#
#
#
#
#
"""See docstring for JSSImporter class."""
from collections import OrderedDict from distutils.version import StrictVersion import os import shutil import sys from xml.etree import ElementTree
import jss
try: from jss import version as PYTHON_JSS_VERSION except ImportError: PYTHON_JSS_VERSION = '0.0.0' from autopkglib import Processor, ProcessorError
all = ["JSSImporter"] version = '0.5.1' REQUIRED_PYTHON_JSS_VERSION = StrictVersion('1.4.0')
class JSSImporter(Processor): """Uploads a package to configured Casper distribution points, and optionally creates supporting categories, computer groups, policy, self service icon, extension attributes, and scripts.
File paths to support files are searched for in order:
1. Path as specified.
2. The parent folder of the path.
3. First ParentRecipe's folder.
4. First ParentRecipe's parent folder.
5. Second ParentRecipe's folder.
6. Second ParentRecipe's parent folder.
7. Nth ParentRecipe's folder.
8. Nth ParentRecipe's parent folder.
This search-path method is primarily in place to support using
recipe overrides. It applies to policy_template, computer group
templates, self_service_icon, script templates, and extension
attribute templates. It allows users to avoid having to copy the
file to the override directory for each recipe.
"""
input_variables = {
"prod_name": {
"required": True,
"description": "Name of the product.",
},
"jss_inventory_name": {
"required": False,
"description": "Smart groups using the 'Application Title' "
"criteria need to specify the app's filename, as registered in "
"the JSS's inventory. If this variable is left out, it will "
"generate an 'Application Title' by adding '.app' to the "
"prod_name, e.g. prod_name='Google Chrome', calculated "
" jss_inventory_name='Google Chrome.app'. If you need to override "
"this behavior, specify the correct name with this variable.",
},
"pkg_path": {
"required": True,
"description": "Path to a pkg or dmg to import - provided by "
"previous pkg recipe/processor.",
},
"version": {
"required": True,
"description": "Version number of software to import - provided "
"by previous pkg recipe/processor.",
},
"JSS_REPOS": {
"required": False,
"description": "Array of dicts for each intended distribution "
"point. Each distribution point type requires slightly different "
"configuration keys and data. Please consult the documentation. ",
"default": [],
},
"JSS_URL": {
"required": True,
"description": "URL to a JSS that api the user has write access "
"to, optionally set as a key in the com.github.autopkg preference "
"file.",
},
"API_USERNAME": {
"required": True,
"description": "Username of account with appropriate access to "
"jss, optionally set as a key in the com.github.autopkg "
"preference file.",
},
"API_PASSWORD": {
"required": True,
"description": "Password of api user, optionally set as a key in "
"the com.github.autopkg preference file.",
},
"JSS_VERIFY_SSL": {
"required": False,
"description": "If set to False, SSL verification in communication"
" with the JSS will be skipped. Defaults to 'True'.",
"default": True,
},
"JSS_MIGRATED": {
"required": False,
"description": "Set to True if you use an AFP or SMB share *and* "
"you have migrated your JSS. Defaults to 'False'.",
"default": False,
},
"JSS_SUPPRESS_WARNINGS": {
"required": False,
"description": "If you want to see urllib3 ssl warnings, enable "
"this.",
"default": True,
},
"category": {
"required": False,
"description": "Category to create/associate imported app "
"package with. Defaults to 'No category assigned'.",
},
"policy_category": {
"required": False,
"description": "Category to create/associate policy with. Defaults"
" to 'No category assigned'.",
},
"os_requirements": {
"required": False,
"description": "Comma-seperated list of OS version numbers to "
"allow. Corresponds to the OS Requirements field for packages. "
"The character 'x' may be used as a wildcard, as in '10.9.x'",
"default": ""
},
"package_info": {
"required": False,
"description": "Text to apply to the package's Info field.",
"default": ""
},
"package_notes": {
"required": False,
"description": "Text to apply to the package's Notes field.",
"default": ""
},
"groups": {
"required": False,
"description": "Array of group dictionaries. Wrap each group in a "
"dictionary. Group keys include 'name' (Name of the group to use, "
"required), 'smart' (Boolean: static group=False, "
"smart group=True, default is False, not required), and "
"'template_path' (string: path to template file to use for group, "
"required for smart groups, invalid for static groups)",
},
"scripts": {
"required": False,
"description": "Array of script dictionaries. Wrap each script in "
"a dictionary. Script keys include 'name' (Name of the script to "
"use, required), 'template_path' (string: path to template file to"
" use for script, required)",
},
"extension_attributes": {
"required": False,
"description": "Array of extension attribute dictionaries. Wrap "
"each extension attribute in a dictionary. Script keys include: "
"'ext_attribute_path' (string: path to extension attribute file.)",
},
"policy_template": {
"required": False,
"description": "Filename of policy template file. If key is "
"missing or value is blank, policy creation will be skipped.",
"default": '',
},
"self_service_description": {
"required": False,
"description": "Use to populate the %SELF_SERVICE_DESCRIPTION% "
"variable for use in templates. Primary use is for filling the "
"info button text in Self Service, but could be used elsewhere.",
"default": '',
},
"self_service_icon": {
"required": False,
"description": "Path to an icon file. Use to add an icon to a "
"self-service enabled policy. Because of the way Casper handles "
"this, the JSSImporter will only upload if the icon's filename is "
"different than the one set on the policy (if it even exists). "
"Please see the README for more information.",
"default": '',
},
"site_id": {
"required": False,
"description": "ID of the target Site",
},
"site_name": {
"required": False,
"description": "Name of the target Site",
},
}
output_variables = {
"jss_changed_objects": {
"description": "Dictionary of added or changed values."
},
"jss_importer_summary_result": {
"description": "Description of interesting results."
},
}
description = __doc__
def build_replace_dict(self):
"""Build a dictionary of replacement values based on available
input variables.
"""
# First, add in AutoPkg's env, excluding types that don't make
# sense:
replace_dict = {key: val for key, val in self.env.items() if val is not
None and type(val) in (str, unicode)}
# Next, add in "official" and Legacy input variables.
replace_dict['VERSION'] = self.version
if self.package is not None:
replace_dict['PKG_NAME'] = self.package.name
replace_dict['PROD_NAME'] = self.env.get('prod_name')
if self.env.get('site_id'):
replace_dict['SITE_ID'] = self.env.get('site_id')
if self.env.get('site_name'):
replace_dict['SITE_NAME'] = self.env.get('site_name')
replace_dict['SELF_SERVICE_DESCRIPTION'] = self.env.get(
'self_service_description')
replace_dict['SELF_SERVICE_ICON'] = self.env.get(
'self_service_icon')
# policy_category is not required, so set a default value if
# absent.
replace_dict['POLICY_CATEGORY'] = self.env.get(
"policy_category") or "Unknown"
#if self.env.get("policy_name"):
# replace_dict['POLICY_NAME'] = self.env.get("policy_name")
# Some applications may have a product name that differs from
# the name that the JSS uses for its "Application Title"
# inventory field. If so, you can set it with the
# jss_inventory_name input variable. If this variable is not
# specified, it will just append .app, which is how most apps
# work.
if self.env.get("jss_inventory_name"):
replace_dict['JSS_INVENTORY_NAME'] = self.env.get(
"jss_inventory_name")
else:
replace_dict['JSS_INVENTORY_NAME'] = '%s.app' \
% self.env.get('prod_name')
self.replace_dict = replace_dict
def replace_text(self, text, replace_dict):
"""Substitute items in a text string.
text: A string with embedded %tags%.
replace_dict: A dict, where
key: Corresponds to the % delimited tag in text.
value: Text to swap in.
"""
for key, value in replace_dict.iteritems():
# Wrap our keys in % to match template tags.
text = text.replace("%%%s%%" % key, value)
return text
def handle_category(self, category_type):
"""Ensure a category is present."""
if self.env.get(category_type):
category_name = self.env.get(category_type)
try:
category = self.j.Category(category_name)
self.output("Category type: %s-'%s' already exists "
"according to JSS, moving on..." %
(category_type, category_name))
except jss.JSSGetError:
# Category doesn't exist
category = jss.Category(self.j, category_name)
category.save()
self.output("Category type: %s-'%s' created." % (category_type,
category_name))
self.env["jss_changed_objects"]["jss_category_added"].append(
category_name)
else:
category = None
return category
def handle_package(self):
"""Creates or updates a package object on the jss, then uploads
a package file to the configured distribution points.
This will only upload a package if a file with the same name
does not already exist on a DP. If you need to force a
re-upload, you must delete the package on the DP first.
Further, if you are using a JDS, it will only upload a package
if a package object with a filename matching the AutoPkg
filename does not exist. If you need to force a re-upload to a
JDS, please delete the package object through the web interface
first.
"""
# Skip package handling if there is no package or repos.
if self.env["JSS_REPOS"] and self.env["pkg_path"] != '':
os_requirements = self.env.get("os_requirements")
package_info = self.env.get("package_info")
package_notes = self.env.get("package_notes")
# See if the package is non-flat (requires zipping prior to
# upload).
if os.path.isdir(self.env['pkg_path']):
shutil.make_archive(self.env['pkg_path'], 'zip',
os.path.dirname(self.env['pkg_path']),
self.pkg_name)
self.env['pkg_path'] += '.zip'
self.pkg_name += '.zip'
try:
package = self.j.Package(self.pkg_name)
self.output("Pkg-object already exists according to JSS, "
"moving on...")
# TODO: 4x of basically the same block code smell!
# Set os_requirements if they don't match.
if os_requirements != package.findtext("os_requirements"):
package.set_os_requirements(os_requirements)
package.save()
self.output("Package os_requirements updated.")
self.env["jss_changed_objects"][
"jss_package_updated"].append(package.name)
# Set package_info if they don't match.
if package_info != package.findtext("info"):
package.find("info").text = package_info
package.save()
self.output("Package info field updated.")
self.env["jss_changed_objects"][
"jss_package_updated"].append(package.name)
# Set package_notes if they don't match.
if package_notes != package.findtext("notes"):
package.find("notes").text = package_notes
package.save()
self.output("Package notes field updated.")
self.env["jss_changed_objects"][
"jss_package_updated"].append(package.name)
# Update category if necessary.
if self.category is not None:
recipe_name = self.category.name
else:
recipe_name = 'Unknown'
if package.find('category').text != recipe_name:
package.find('category').text = recipe_name
package.save()
self.output("Package category updated.")
self.env["jss_changed_objects"][
"jss_package_updated"].append(package.name)
except jss.JSSGetError:
# Package doesn't exist
if self.category is not None:
package = jss.Package(self.j, self.pkg_name,
category=self.category.name)
else:
package = jss.Package(self.j, self.pkg_name)
package.set_os_requirements(os_requirements)
package.find("info").text = package_info
package.find("notes").text = package_notes
package.save()
self.env["jss_changed_objects"]["jss_package_added"].append(package.name)
# Ensure packages are on distribution point(s)
# If we had to make a new package object, we know we need to
# copy the package file, regardless of DP type. This solves
# the issue regarding the JDS.exists() method: See
# python-jss docs for info. The problem with this method is
# that if you cancel an AutoPkg run and the package object
# has been created, but not uploaded, you will need to
# delete the package object from the JSS before running a
# recipe again or it won't upload the package file.
#
# Passes the id of the newly created package object so JDS'
# will upload to the correct package object. Ignored by
# AFP/SMB.
if self.env["jss_changed_objects"]["jss_package_added"]:
self._copy(self.env["pkg_path"], id_=package.id)
# For AFP/SMB shares, we still want to see if the package
# exists. If it's missing, copy it!
elif not self.j.distribution_points.exists(
os.path.basename(self.env["pkg_path"])):
self._copy(self.env["pkg_path"])
else:
self.output("Package upload not needed.")
else:
package = None
self.output("Package upload and object update skipped. If this is "
"a mistake, ensure you have JSS_REPOS configured.")
return package
def _copy(self, source_item, id_=-1):
"""Copy a package or script using the JSS_REPOS preference."""
self.output("Copying %s to all distribution points." % source_item)
def output_copy_status(connection):
"""Output AutoPkg copying status."""
self.output("Copying to %s" % connection["url"])
self.j.distribution_points.copy(source_item, id_=id_,
pre_callback=output_copy_status)
self.env["jss_changed_objects"]["jss_repo_updated"].append(
os.path.basename(source_item))
self.output("Copied %s" % source_item)
def handle_groups(self):
"""Manage group existence and creation."""
groups = self.env.get('groups')
computer_groups = []
if groups:
for group in groups:
if self._validate_input_var(group):
# TODO: There is a proper property for this now.
is_smart = group.get('smart', False)
if is_smart:
computer_group = self._add_or_update_smart_group(group)
else:
computer_group = \
self._add_or_update_static_group(group)
computer_groups.append(computer_group)
return computer_groups
def _add_or_update_static_group(self, group):
"""Given a group, either add a new group or update existing
group.
"""
# Check for pre-existing group first
try:
computer_group = self.j.ComputerGroup(group['name'])
self.output("Computer Group: %s already exists." %
computer_group.name)
except jss.JSSGetError:
computer_group = jss.ComputerGroup(self.j, group['name'])
computer_group.save()
self.output("Computer Group: %s created." % computer_group.name)
self.env["jss_changed_objects"]["jss_group_added"].append(
computer_group.name)
return computer_group
def _add_or_update_smart_group(self, group):
"""Given a group, either add a new group or update existing
group.
"""
# Build the template group object
self.replace_dict['group_name'] = group['name']
if group.get('site_id'):
self.replace_dict['site_id'] = group.get('site_id')
if group.get('site_name'):
self.replace_dict['site_name'] = group.get('site_name')
computer_group = self._update_or_create_new(
jss.ComputerGroup, group["template_path"],
update_env="jss_group_updated", added_env="jss_group_added")
return computer_group
def _update_or_create_new(self, obj_cls, template_path, name='',
added_env='', update_env=''):
"""Check for an existing object and update it, or create a new
object.
Args:
obj_cls: The python-jss object class to work with.
template_path: String filename or path to the template
file. See get_templated_object() for more info.
name: The name to use. Defaults to the "name" property of
the templated object.
added_env: The environment var to update if an object is
added.
update_env: The environment var to update if an object is
updated.
Returns:
The recipe object after updating.
"""
# Create a new object from the template
recipe_object = self.get_templated_object(obj_cls, template_path)
if not name:
name = recipe_object.name
# Check for an existing object with this name.
existing_object = None
try:
existing_object = self.j.factory.get_object(obj_cls, name)
except jss.JSSGetError:
pass
# If object is a Policy, we need to inject scope, scripts,
# package, and an icon.
if obj_cls is jss.Policy:
if existing_object is not None:
# If this policy already exists, and it has an icon set,
# copy its icon section to our template, as we have no
# other way of getting this information.
icon_xml = existing_object.find(
'self_service/self_service_icon')
if icon_xml is not None:
self.add_icon_to_policy(recipe_object, icon_xml)
self.add_scope_to_policy(recipe_object)
self.add_scripts_to_policy(recipe_object)
self.add_package_to_policy(recipe_object)
if existing_object is not None:
# Update the existing object.
url = existing_object.get_object_url()
self.j.put(url, recipe_object)
# Retrieve the updated XML.
recipe_object = self.j.factory.get_object(obj_cls, name)
self.output("%s: %s updated." % (obj_cls.__name__, name))
if update_env:
self.env["jss_changed_objects"][update_env].append(name)
else:
# Object doesn't exist yet.
recipe_object.save()
self.output("%s: %s created." % (obj_cls.__name__, name))
if added_env:
self.env["jss_changed_objects"][added_env].append(name)
return recipe_object
def get_templated_object(self, obj_cls, template_path):
"""Return an object based on a template located in search path.
Args:
obj_cls: JSSObject class (for the purposes of JSSIMporter a
Policy or a ComputerGroup)
template_path: String filename or path to template file.
See find_file_in_search_path() for more information on
file searching.
Returns:
A JSS Object created based on the template,
post-text-replacement.
"""
final_template_path = self.find_file_in_search_path(template_path)
# Open and return a new object.
with open(final_template_path, 'r') as template_file:
text = template_file.read()
template = self.replace_text(text, self.replace_dict)
return obj_cls.from_string(self.j, template)
def find_file_in_search_path(self, path):
"""Search search_paths for the first existing instance of path.
Searches, in order, through the following directories
until a matching file is found:
1. Path as specified.
2. The parent folder of the path.
3. First ParentRecipe's folder.
4. First ParentRecipe's parent folder.
5. Second ParentRecipe's folder.
6. Second ParentRecipe's parent folder.
7. Nth ParentRecipe's folder.
8. Nth ParentRecipe's parent folder.
This search-path method is primarily in place to
support using recipe overrides. It allows users to avoid having
to copy templates, icons, etc, to the override directory.
Args:
obj_cls: JSSObject class (for the purposes of JSSIMporter a
Policy or a ComputerGroup)
path: String filename or path to file.
If path is just a filename, path is assumed to
be self.env["RECIPE_DIR"].
Returns:
Absolute path to the first match in search paths.
Raises:
ProcessorError if none of the above files exist.
"""
# Ensure input is expanded.
path = os.path.expanduser(path)
# Check to see if path is a filename.
if not os.path.dirname(path):
# If so, assume that the file is meant to be in the recipe
# directory.
path = os.path.join(self.env["RECIPE_DIR"], path)
filename = os.path.basename(path)
parent_recipe_dirs = [os.path.dirname(parent) for parent in
self.env["PARENT_RECIPES"]]
unique_parent_dirs = OrderedDict()
for parent in parent_recipe_dirs:
unique_parent_dirs[parent] = parent
search_dirs = ([os.path.dirname(path)] + unique_parent_dirs.keys())
tested = []
final_path = ""
# Look for the first file that exists in the search_dirs and
# their parent folders.
for dir in search_dirs:
test_path = os.path.join(dir, filename)
test_parent_folder_path = os.path.abspath(
os.path.join(dir, "..", filename))
if os.path.exists(test_path):
final_path = test_path
elif os.path.exists(test_parent_folder_path):
final_path = test_parent_folder_path
tested.append(test_path)
tested.append(test_parent_folder_path)
if final_path:
self.output("Found file: %s" % final_path)
break
if not final_path:
raise IOError("Unable to find file %s at any of the following "
"locations: %s" % (filename, tested))
return final_path
def _validate_input_var(self, var):
"""Validate the value before trying to add a group.
Returns False if dictionary has invalid values, or True if it
seems okay.
"""
# Skipping non-string values:
# Does group name or template have a replacement var
# that has not been replaced?
# Does the group have a blank value? (A blank value isn't really
# invalid, but there's no need to process it further.)
invalid = [False for value in var.values() if isinstance(value, str)
and (value.startswith('%') and value.endswith('%')) or not
value]
if invalid:
result = False
else:
result = True
return result
def handle_scripts(self):
"""Add scripts if needed."""
scripts = self.env.get('scripts')
results = []
if scripts:
for script in scripts:
script_file = self.find_file_in_search_path(
script["name"])
script_object = self._update_or_create_new(
jss.Script,
script["template_path"],
os.path.basename(script_file),
added_env="jss_script_added",
update_env="jss_script_updated")
# Copy the script to the distribution points.
self._copy(script_file, id_=script_object.id)
results.append(script_object)
return results
def handle_extension_attributes(self):
"""Add extension attributes if needed."""
extattrs = self.env.get('extension_attributes')
results = []
if extattrs:
for extattr in extattrs:
extattr_object = self._update_or_create_new(
jss.ComputerExtensionAttribute,
extattr['ext_attribute_path'],
update_env="jss_extension_attribute_added",
added_env="jss_extension_attribute_updated")
results.append(extattr_object)
return results
def handle_icon(self):
"""Add self service icon if needed."""
# Icons are tricky. The only way to add new ones is to use
# FileUploads. If you manually upload them, you can add them to
# a policy to get their ID, but there is no way to query the JSS
# to see what icons are available. Thus, icon handling involves
# several cooperating methods. If we just add an icon every
# time we run a recipe, however, we end up with a ton of
# redundent icons, and short of actually deleting them in the
# sql database, there's no way to delete icons. So when we run,
# we first check for an existing policy, and if it exists, copy
# its icon XML, which is then added to the templated Policy. If
# there is no icon information, but the recipe specifies one,
# then FileUpload it up.
# If no policy handling is desired, we can't upload an icon.
if self.env.get("self_service_icon") and self.policy is not None:
# Search through search-paths for icon file.
icon_path = self.find_file_in_search_path(
self.env["self_service_icon"])
icon_filename = os.path.basename(icon_path)
# Compare the filename in the policy to the one provided by
# the recipe. If they don't match, we need to upload a new
# icon.
policy_filename = self.policy.findtext(
'self_service/self_service_icon/filename')
if not policy_filename == icon_filename:
icon = jss.FileUpload(self.j, 'policies', 'id', self.policy.id,
icon_path)
icon.save()
self.env["jss_changed_objects"]["jss_icon_uploaded"].append(
icon_filename)
self.output("Icon uploaded to JSS.")
else:
self.output("Icon matches existing icon, moving on...")
def handle_policy(self):
"""Create or update a policy."""
if self.env.get("policy_template"):
template_filename = self.env.get("policy_template")
policy = self._update_or_create_new(jss.Policy, template_filename,
update_env="jss_policy_updated",
added_env="jss_policy_added")
else:
self.output("Policy creation not desired, moving on...")
policy = None
return policy
def add_scope_to_policy(self, policy_template):
"""Incorporate scoping groups into a policy."""
computer_groups_element = self.ensure_XML_structure(
policy_template, 'scope/computer_groups')
for group in self.groups:
policy_template.add_object_to_path(group, computer_groups_element)
def add_scripts_to_policy(self, policy_template):
"""Incorporate scripts into a policy."""
scripts_element = self.ensure_XML_structure(policy_template, 'scripts')
for script in self.scripts:
script_element = policy_template.add_object_to_path(
script, scripts_element)
priority = ElementTree.SubElement(script_element, 'priority')
priority.text = script.findtext('priority')
def add_package_to_policy(self, policy_template):
"""Add a package to a self service policy."""
if self.package is not None:
packages_element = self.ensure_XML_structure(
policy_template, 'package_configuration/packages')
policy_template.add_package(self.package)
def add_icon_to_policy(self, policy_template, icon_xml):
"""Add an icon to a self service policy."""
self_service_icon_element = self.ensure_XML_structure(
policy_template, 'self_service')
self_service = policy_template.find('self_service')
self_service.append(icon_xml)
def ensure_XML_structure(self, element, path):
"""Given an XML path and a starting element, ensure that all
tiers of the hierarchy exist.
"""
search, slash, path = path.partition('/')
if search:
if element.find(search) is None:
ElementTree.SubElement(element, search)
return self.ensure_XML_structure(element.find(search), path)
return element
def get_report_string(self, items):
"""Return a nice human-readable string from a list of JSS
objects.
"""
return ', '.join(set(items))
def summarize(self):
"""If anything has been added or updated, report back to
AutoPkg.
"""
# Only summarize if something has happened.
if [True for value in self.env["jss_changed_objects"].values() if
value]:
# Create a blank summary.
self.env['jss_importer_summary_result'] = {
'summary_text': 'The following changes were made to the JSS:',
'report_fields': ['Package', 'Categories', 'Groups', 'Scripts',
'Extension Attributes', 'Policy', 'Icon'],
'data': {
'Package': '',
'Categories': '',
'Groups': '',
'Scripts': '',
'Extension Attributes': '',
'Policy': '',
'Icon': ''
}
}
# Shortcut variables for lovely code conciseness
changes = self.env["jss_changed_objects"]
data = self.env["jss_importer_summary_result"]["data"]
package = self.get_report_string(changes["jss_package_added"] +
changes["jss_package_updated"])
if package:
data["Package"] = package
policy = changes["jss_policy_updated"] + \
changes["jss_policy_added"]
if policy:
data['Policy'] = self.get_report_string(policy)
if changes["jss_icon_uploaded"]:
data['Icon'] = os.path.basename(self.env['self_service_icon'])
# Get nice strings for our list-types.
if changes["jss_category_added"]:
data["Categories"] = self.get_report_string(
changes["jss_category_added"])
groups = changes["jss_group_updated"] + changes["jss_group_added"]
if groups:
data["Groups"] = self.get_report_string(groups)
scripts = changes["jss_script_updated"] + \
changes["jss_script_added"]
if scripts:
data["Scripts"] = self.get_report_string(scripts)
extattrs = changes["jss_extension_attribute_updated"] + \
changes["jss_extension_attribute_added"]
if extattrs:
data["Extension Attributes"] = self.get_report_string(extattrs)
def init_jss_changed_objects(self):
"""Build a dictionary to track changes to JSS objects for
later reporting.
"""
self.env["jss_changed_objects"] = {
"jss_repo_updated": [],
"jss_category_added": [],
"jss_package_added": [],
"jss_package_updated": [],
"jss_group_added": [],
"jss_group_updated": [],
"jss_script_added": [],
"jss_script_updated": [],
"jss_extension_attribute_added": [],
"jss_extension_attribute_updated": [],
"jss_policy_added": [],
"jss_policy_updated": [],
"jss_icon_uploaded": []}
def main(self):
"""Main processor code."""
# Ensure we have the right version of python-jss
python_jss_version = StrictVersion(PYTHON_JSS_VERSION)
if python_jss_version < REQUIRED_PYTHON_JSS_VERSION:
self.output("Requires python-jss version: %s. Installed: %s" %
(REQUIRED_PYTHON_JSS_VERSION, python_jss_version))
sys.exit()
# clear any pre-existing summary result
if 'jss_importer_summary_result' in self.env:
del self.env['jss_importer_summary_result']
# pull jss recipe-specific args, prep api auth
repoUrl = self.env["JSS_URL"]
authUser = self.env["API_USERNAME"]
authPass = self.env["API_PASSWORD"]
sslVerify = self.env["JSS_VERIFY_SSL"]
jss_migrated = self.env["JSS_MIGRATED"]
suppress_warnings = self.env["JSS_SUPPRESS_WARNINGS"]
repos = self.env["JSS_REPOS"]
self.j = jss.JSS(url=repoUrl, user=authUser, password=authPass,
ssl_verify=sslVerify, repo_prefs=repos,
jss_migrated=jss_migrated,
suppress_warnings=suppress_warnings)
self.pkg_name = os.path.basename(self.env["pkg_path"])
self.prod_name = self.env["prod_name"]
self.version = self.env["version"]
# Build and init jss_changed_objects
self.init_jss_changed_objects()
self.category = self.handle_category("category")
self.policy_category = self.handle_category("policy_category")
# Get our DPs read for copying.
self.j.distribution_points.mount()
self.package = self.handle_package()
# Build our text replacement dictionary
self.build_replace_dict()
self.extattrs = self.handle_extension_attributes()
self.groups = self.handle_groups()
self.scripts = self.handle_scripts()
self.policy = self.handle_policy()
self.handle_icon()
# Done with DPs, unmount them.
self.j.distribution_points.umount()
self.summarize()
if name == "main": processor = JSSImporter() processor.execute_shell()
It's hard to say, since AutoPkg doesn't include the full traceback from the exception. However, based on the output, it seems like it's dying somewhere in the group handling section.
I suspect that there's probably something up with the values in the recipe vs. the smart group template.
Cool what do you think I should do exactly? I'm not using the smart group template so should I comment that functionality out? What do you think I should look for in the values in the recipes?
I think that if we can take a look at all of the assorted files you are combining to provide this software, we can figure out what is going on. You'll want to make sure there's nothing like passwords, usernames, or URLs in what you provide.
Probably need: the jss recipe all templates in use (smart group, extension attribute, script, policy) override recipe if there is one.
Okay here's the JSS recipe which I had as an override. I've modified just the category and name input values. Your original recipe is unchanged and all template files are the same as you have in your repo.
<?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">
And here's the copy of the run..
Processing AdobeFlashPlayer.jss... AdobeFlashURLProvider AdobeFlashURLProvider: Found URL http://fpdownload.macromedia.com/get/flashplayer/pdc/22.0.0.192/install_flash_player_osx.dmg URLDownloader URLDownloader: Item at URL is unchanged. URLDownloader: Using existing /var/root/Library/AutoPkg/Cache/com.github.sggr57a.jss.flashplayer/downloads/Adobe Flash Player.dmg EndOfCheckPhase CodeSignatureVerifier CodeSignatureVerifier: Mounted disk image /var/root/Library/AutoPkg/Cache/com.github.sggr57a.jss.flashplayer/downloads/Adobe Flash Player.dmg CodeSignatureVerifier: Verifying installer package signature... CodeSignatureVerifier: Package "Adobe Flash Player.pkg": CodeSignatureVerifier: Status: signed by a certificate trusted by Mac OS X CodeSignatureVerifier: Certificate Chain: CodeSignatureVerifier: 1. Developer ID Installer: Adobe Systems, Inc. CodeSignatureVerifier: SHA1 fingerprint: 9D 75 C9 20 01 4A 65 04 94 A7 63 95 E3 91 93 47 04 E8 57 DF CodeSignatureVerifier: ----------------------------------------------------------------------------- CodeSignatureVerifier: 2. Developer ID Certification Authority CodeSignatureVerifier: SHA1 fingerprint: 3B 16 6C 3B 7D C4 B7 51 C9 FE 2A FA B9 13 56 41 E3 88 E1 86 CodeSignatureVerifier: ----------------------------------------------------------------------------- CodeSignatureVerifier: 3. Apple Root CA CodeSignatureVerifier: SHA1 fingerprint: 61 1E 5B 66 2C 59 3A 08 FF 58 D1 4A E2 24 52 D1 98 DF 6C 60 CodeSignatureVerifier: CodeSignatureVerifier: Signature is valid CodeSignatureVerifier: Authority name chain is valid Versioner Versioner: Mounted disk image /var/root/Library/AutoPkg/Cache/com.github.sggr57a.jss.flashplayer/downloads/Adobe Flash Player.dmg Versioner: Found version 22.0.0.192 in file /private/tmp/dmg.KP6Qrv/Install Adobe Flash Player.app/Contents/Info.plist PkgCopier PkgCopier: Mounted disk image /var/root/Library/AutoPkg/Cache/com.github.sggr57a.jss.flashplayer/downloads/Adobe Flash Player.dmg PkgCopier: Copied /private/tmp/dmg.jLDWfy/Install Adobe Flash Player.app/Contents/Resources/Adobe Flash Player.pkg to /var/root/Library/AutoPkg/Cache/com.github.sggr57a.jss.flashplayer/Adobe Flash Player 22.0.0.192.pkg JSSImporter JSSImporter: Category type: category-'Testing' already exists according to JSS, moving on... JSSImporter: Category type: policy_category-'Testing' already exists according to JSS, moving on... JSSImporter: Pkg-object already exists according to JSS, moving on... 'NoneType' object has no attribute 'exists' Failed. Receipt written to /var/root/Library/AutoPkg/Cache/com.github.sggr57a.jss.flashplayer/receipts/AdobeFlashPlayer-receipt-20160711-104017.plist
The following recipes failed: AdobeFlashPlayer.jss Error in com.github.sggr57a.jss.flashplayer: Processor: JSSImporter: Error: 'NoneType' object has no attribute 'exists'
The following packages were copied: Pkg Path
/var/root/Library/AutoPkg/Cache/com.github.sggr57a.jss.flashplayer/Adobe Flash Player 22.0.0.192.pkg
Does anything jump out to you? I get the same results no matter which recipe I use.
Seeing the same:
JSSImporter: No value supplied for package_info, setting default value of:
JSSImporter: No value supplied for package_notes, setting default value of:
JSSImporter: No value supplied for JSS_MIGRATED, setting default value of: False
JSSImporter: No value supplied for os_requirements, setting default value of:
JSSImporter: No value supplied for JSS_SUPPRESS_WARNINGS, setting default value of: True
JSSImporter: Category type: category-'Productivity' already exists according to JSS, moving on...
JSSImporter: Category type: policy_category-'Testing' already exists according to JSS, moving on...
JSSImporter: Pkg-object already exists according to JSS, moving on...
'NoneType' object has no attribute 'exists'
Failed.
There is not a PKG on the distribution point, but a "stub" record for the pkg is created in the JSS.
Did some more testing, & seems that the ReadWrite credentials for the DP were wrong. @sggr57a something to check?
To add some more information, I used JSSImporter with a fresh test instance and forgot to add a DP. So this error can be shown if there are no DP's defined in the JSS.
I think it's probably worth having an error message for people as stupid as me, so I'll submit a PR on that.
@sggr57a This error is almost certainly because you are trying to use the automatic distribution point configuration (I.e. your AutoPkg preference file just has a name
and password
for each repo) and the name for at least one of them doesn't match the name on the JSS.
In the current beta I have it throwing an exception and stopping if this is the case, with a hopefully useful error message, so hopefully others won't see this in the future.
I'll also add a message pointing out that you don't have any DPs, which seems strange to me to do, but people do intentionally.
I'm receiving this error when trying to run this and any jss recipe.
The following recipes failed: AdobeFlashPlayer.jss Error in com.github.autopkg.jss.AdobeFlashPlayer: Processor: JSSImporter: Error: 'NoneType' object has no attribute 'exists'