Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

2024 Jamf API Updates and New Features #41

Open
wants to merge 30 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
44e7fff
Adding config file
rustymyers Oct 18, 2023
7891597
Adding Requirements, configparser to download.py
rustymyers Oct 18, 2023
af81b87
Adding token auth, configparser, and export_path
rustymyers Oct 18, 2023
8029a4c
Adding token auth, configparser, and sync_path
rustymyers Oct 18, 2023
01cf00f
Update sync.py
rustymyers Oct 21, 2023
d51f998
Update sync.py
rustymyers Oct 21, 2023
08ba3e3
Update sync.py
rustymyers Oct 21, 2023
b651837
Update README.md
rustymyers Oct 21, 2023
0b98075
Update download.py
rustymyers Oct 21, 2023
2dcbe32
Merge branch 'master' of https://github.com/badstreff/git2jss
rustymyers Oct 21, 2023
5eb0183
Update sync.py
rustymyers Oct 21, 2023
e586502
Update download.py
rustymyers Oct 21, 2023
d78edb4
Black formatter
rustymyers Oct 23, 2023
4b38a65
Updated download.py using Black auto formatter
rustymyers Oct 24, 2023
a56f956
Removnig missing config file message
rustymyers Oct 24, 2023
c8ed1a2
Fixing "is not" testing a litereal to "!="
rustymyers Oct 24, 2023
74670ef
Updating readme badge and bullet list
rustymyers Oct 30, 2023
bdec1ce
Fixing duplicate imports and functions, removing token when completed
rustymyers Oct 30, 2023
deb0401
Removing except and updating invalidate token call
rustymyers Oct 30, 2023
a5b2188
Adding timeout for requests
rustymyers Oct 30, 2023
39bf70d
Adding exception type for username
rustymyers Oct 31, 2023
7bf580d
Adding configparser.NoOptionError exception
rustymyers Nov 1, 2023
91a1edc
Merge branch 'master' of https://github.com/badstreff/git2jss
rustymyers Nov 1, 2023
f8f49d3
Revert "Merge branch 'master' of https://github.com/badstreff/git2jss"
rustymyers Nov 1, 2023
5df38bc
Updating codacy to check tools folder
rustymyers Nov 1, 2023
3ae4671
defusedxml and timeouts for download.sh
rustymyers Nov 1, 2023
6bb56e9
Updating Errors and CI tests
rustymyers Nov 1, 2023
2749dc5
Adding exception type to download, compressing configs for verify
rustymyers Nov 1, 2023
1306f48
Update verifyEA.py
rustymyers Nov 1, 2023
06d19b9
Update validate xml to work on folders except exclude. Removing pass …
rustymyers Nov 1, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .codacy.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
---
exclude_paths:
- 'aiojss/**'
- 'tools/**'
- 'scripts/**'
- 'extension_attributes/**'
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,6 @@ extension_attributes/*
!extension_attributes/Last User/
scripts/*
!scripts/Install Software Updates/
!scripts/templates
!extension_attributes/templates
tools/ci_tests/computers.json
22 changes: 11 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# git2jss
[![Codacy Badge](https://api.codacy.com/project/badge/Grade/d9d618c32e93436ea67102fd3d3f5b21)](https://www.codacy.com/app/adam-furbee/git2jss?utm_source=github.com&utm_medium=referral&utm_content=BadStreff/git2jss&utm_campaign=Badge_Grade)
[![Codacy Badge](https://app.codacy.com/project/badge/Grade/c49c0bd6a88d4f1e8c6808455171178e)](https://app.codacy.com/gh/rustymyers/git2jss/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade)

A fast asynchronous python library for syncing your scripts in git with your JSS easily. This allows admins to keep their script in a version control system for easy updating rather than googling and copy-pasting from resources that they find online.

Expand All @@ -26,16 +26,16 @@ Optional flags for `sync.py`:
- `--verbose` to add additional logging
- `--update_all` to upload all resources in `./extension_attributes` and `./scripts`
- `--jenkins` to write a Jenkins file:`jenkins.properties` with `$scripts` and `$eas` and compare `$GIT_PREVIOUS_COMMIT` with `$GIT_COMMIT`

### [ConfigParser](https://docs.python.org/3/library/configparser.html) (Optional):

A config file can be created in the project root or the users home folder. When a config file exists, the script will not promt for a password.

A jamfapi.cfg file can provide the following variables:

- username
- password
- url
### [ConfigParser](https://docs.python.org/3/library/configparser.html) (Optional):
A config file can be created in the project root or the users home folder. When a config file exists, the script will not promt for a password.
A jamfapi.cfg file can provide the following variables:
- username
- password
- url

### Prerequisites
git2jss requires [Python 3.6](https://www.python.org/downloads/) and the python modules listed in `requirements.txt`
Expand Down
7 changes: 7 additions & 0 deletions extension_attributes/templates/Last User/ea.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/bin/sh
lastUser=`defaults read /Library/Preferences/com.apple.loginwindow lastUserName`

if [ $lastUser == "" ]; then
echo "<result>No logins</result>"
else
echo "<result>$lastUser</result>"
13 changes: 13 additions & 0 deletions extension_attributes/templates/Last User/ea.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?xml version="1.0" ?>
<computer_extension_attribute>
<name>Last User</name>
<enabled>true</enabled>
<description>This attribute displays the last user to log in. This attribute applies to both Mac and Windows.</description>
<data_type>String</data_type>
<input_type>
<type>script</type>
<platform>Mac</platform>
<script/>
</input_type>
<inventory_display>General</inventory_display>
</computer_extension_attribute>
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ cchardet
aiodns
uvloop
requests
configparser
configparser
defusedxml
8 changes: 8 additions & 0 deletions scripts/templates/Install Software Updates/script.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/bin/bash

# Install all software updates
# Example for git2jss

softwareupdate -i -a

exit 0
11 changes: 11 additions & 0 deletions scripts/templates/Install Software Updates/script.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" ?>
<script>
<name>Install Software Updates</name>
<category>No category assigned</category>
<info/>
<notes>Created by Brad Schmidt</notes>
<priority>Before</priority>
<parameters/>
<os_requirements/>
<script_contents/>
</script>
82 changes: 31 additions & 51 deletions sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import os
from os.path import dirname, join, realpath
import sys
import xml.etree.ElementTree as ET
import getpass
import argparse
import logging
Expand All @@ -14,8 +13,7 @@
import uvloop
import configparser
import requests
import configparser
import requests
from defusedxml import ElementTree as eTree

logging.basicConfig(
level=logging.DEBUG,
Expand All @@ -40,7 +38,7 @@ def get_uapi_token():
fetches api token
"""
jamf_test_url = url + "/api/v1/auth/token"
response = requests.post(url=jamf_test_url, auth=(username, password))
response = requests.post(url=jamf_test_url, auth=(username, password), timeout=5)
response_json = response.json()
return response_json["token"]

Expand All @@ -51,27 +49,7 @@ def invalidate_uapi_token(uapi_token):
"""
jamf_test_url = url + "/api/v1/auth/invalidate-token"
headers = {"Accept": "*/*", "Authorization": "Bearer " + uapi_token}
_ = requests.post(url=jamf_test_url, headers=headers)


# https://github.com/lazymutt/Jamf-Pro-API-Sampler/blob/5f8efa92911271248f527e70bd682db79bc600f2/jamf_duplicate_detection.py#L99
def get_uapi_token():
"""
fetches api token
"""
jamf_test_url = url + "/api/v1/auth/token"
response = requests.post(url=jamf_test_url, auth=(username, password))
response_json = response.json()
return response_json["token"]


def invalidate_uapi_token(uapi_token):
"""
invalidates api token
"""
jamf_test_url = url + "/api/v1/auth/invalidate-token"
headers = {"Accept": "*/*", "Authorization": "Bearer " + uapi_token}
_ = requests.post(url=jamf_test_url, headers=headers)
_ = requests.post(url=jamf_test_url, headers=headers, timeout=5)


def check_for_changes():
Expand Down Expand Up @@ -208,7 +186,7 @@ async def upload_extension_attribute(session, url, user, passwd, ext_attr, semap
if has_script and data:
template.find("input_type/script").text = data
if args.verbose:
print(ET.tostring(template))
print(eTree.tostring(template))
print("response status initial get: ", resp.status)
if resp.status == 200:
put_url = (
Expand All @@ -217,12 +195,12 @@ async def upload_extension_attribute(session, url, user, passwd, ext_attr, semap
+ template.find("name").text
)
resp = await session.put(
put_url, data=ET.tostring(template), headers=headers
put_url, data=eTree.tostring(template), headers=headers
)
else:
post_url = url + "/JSSResource/computerextensionattributes/id/0"
resp = await session.post(
post_url, data=ET.tostring(template), headers=headers
post_url, data=eTree.tostring(template), headers=headers
)
if args.verbose:
print("response status: ", resp.status)
Expand All @@ -248,7 +226,7 @@ async def get_ea_template(session, url, user, passwd, ext_attr):
with open(
join(sync_path, "extension_attributes", ext_attr, xml_file[0]), "r"
) as file:
template = ET.fromstring(file.read())
template = eTree.parse(file.read())
except IndexError:
with async_timeout.timeout(args.timeout):
headers = {
Expand All @@ -268,23 +246,25 @@ async def get_ea_template(session, url, user, passwd, ext_attr):
+ ext_attr,
headers=headers,
) as response:
template = ET.fromstring(await response.text())
template = eTree.fromstring(await response.text())
else:
template = ET.parse(join(sync_path, "templates/ea.xml")).getroot()
template = eTree.parse(
join(sync_path, "templates/ea.xml")
).getroot()
# name is mandatory, so we use the foldername if nothing is set in
# a template
if args.verbose:
print(ET.tostring(template))
print(eTree.tostring(template))
if template.find("category") and template.find("category").text not in CATEGORIES:
ET.SubElement(template, "category").text = "None"
eTree.SubElement(template, "category").text = "None"
if args.verbose:
c = template.find("category").text
print(
f"""WARNING: Unable to find category {c} in the JSS,
setting to None"""
)
if template.find("name") is None:
ET.SubElement(template, "name").text = ext_attr
eTree.SubElement(template, "name").text = ext_attr
elif not template.find("name").text or template.find("name").text is None:
template.find("name").text = ext_attr
return template
Expand Down Expand Up @@ -344,12 +324,12 @@ async def upload_script(session, url, user, passwd, script, semaphore):
url + "/JSSResource/scripts/name/" + template.find("name").text
)
resp = await session.put(
put_url, data=ET.tostring(template), headers=headers
put_url, data=eTree.tostring(template), headers=headers
)
else:
post_url = url + "/JSSResource/scripts/id/0"
resp = await session.post(
post_url, data=ET.tostring(template), headers=headers
post_url, data=eTree.tostring(template), headers=headers
)
if resp.status in (201, 200):
print("Uploaded script: %s" % template.find("name").text)
Expand All @@ -369,7 +349,7 @@ async def get_script_template(session, url, user, passwd, script):
]
try:
with open(join(sync_path, "scripts", script, xml_file[0]), "r") as file:
template = ET.fromstring(file.read())
template = eTree.fromstring(file.read())
except IndexError:
with async_timeout.timeout(args.timeout):
headers = {
Expand All @@ -384,14 +364,14 @@ async def get_script_template(session, url, user, passwd, script):
async with session.get(
url + "/JSSResource/scripts/name/" + script, headers=headers
) as response:
template = ET.fromstring(await response.text())
template = eTree.fromstring(await response.text())
else:
template = ET.parse(
template = eTree.parse(
join(sync_path, "templates/script.xml")
).getroot()
# name is mandatory, so we use the filename if nothing is set in a template
if args.verbose:
print(ET.tostring(template))
print(eTree.tostring(template))
if (
template.find("category") is not None
and template.find("category").text not in CATEGORIES
Expand All @@ -404,7 +384,7 @@ async def get_script_template(session, url, user, passwd, script):
setting to None"""
)
if template.find("name") is None:
ET.SubElement(template, "name").text = script
eTree.SubElement(template, "name").text = script
elif not template.find("name").text or template.find("name").text is None:
template.find("name").text = script
return template
Expand All @@ -427,7 +407,7 @@ async def get_existing_categories(session, url, user, passwd, semaphore):
c.find("name").text
for c in [
e
for e in ET.fromstring(await resp.text()).findall(
for e in eTree.fromstring(await resp.text()).findall(
"category"
)
]
Expand Down Expand Up @@ -490,26 +470,23 @@ async def main():
CONFIG_FILE = config_path

if CONFIG_FILE != "":
try:
# Get config
CONFPARSER.read(CONFIG_FILE)
except:
print("Can't read config file")
# Get config
CONFPARSER.read(CONFIG_FILE)
try:
username = CONFPARSER.get("jss", "username")
except:
except configparser.NoOptionError:
print("Can't find username in configfile")
try:
password = CONFPARSER.get("jss", "password")
except:
except configparser.NoOptionError:
print("Can't find password in configfile")
try:
url = CONFPARSER.get("jss", "server")
except:
except configparser.NoOptionError:
print("Can't find url in configfile")
try:
sync_path = CONFPARSER.get("jss", "sync_path")
except:
except configparser.NoOptionError:
print("Can't find sync_path in config")

# Ask for password if not supplied via command line args
Expand Down Expand Up @@ -537,3 +514,6 @@ async def main():
warnings.simplefilter("always", ResourceWarning)

loop.run_until_complete(main())

# Remove token
invalidate_uapi_token(token)
16 changes: 8 additions & 8 deletions tools/ci_tests/validate_files_and_folders.sh
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,20 @@

#Load up some variables
#Define scripts and templates folders
scripts=$(ls -p scripts | grep -v '/$' | sed -e 's/\..*$//')
scripts_templates=$(ls -p scripts/templates/| sed -e 's/\..*$//')
scripts=$(ls -1 scripts | grep -v "templates")
scripts_templates=$(ls -1 scripts/templates/)

#Define EA and templates
extensionattributes=$(ls -p extension_attributes | grep -v '/$' | sed -e 's/\..*$//')
extensionattributes_templates=$(ls -p extension_attributes/templates/| sed -e 's/\..*$//')
extensionattributes=$(ls -1 extension_attributes | grep -v "templates")
extensionattributes_templates=$(ls -1 extension_attributes/templates/)


#Validate both the Script and the Template for the Script exist.
echo "Making sure files exist in both places in scripts and scripts/Templates"
echo "Making sure files with same names exist in both places in scripts and scripts/Templates"
scriptcompare=$(sdiff -bBWsw 75 <(echo "$scripts") <(echo "$scripts_templates" ))

if [ "$scriptcompare" == "" ]; then
echo "Script and Script Template Exist All good in the hood!"
echo "Script and Script Template Exist in both folders!"
else
echo "Errors! occurred please correct the below"
echo " Scripts | Templates"
Expand All @@ -31,10 +31,10 @@ fi


#Valate both the EA and the Template for the EA exist.
echo "Making sure files exist in both places extension_attributes and extension_attributes/Templates"
echo "Making sure files with same names exist in both places extension_attributes and extension_attributes/Templates"
eacompare=$(sdiff -bBWsw 75 <(echo "$extensionattributes") <(echo "$extensionattributes_templates"))
if [ "$eacompare" == "" ]; then
echo "EA and EA Template Exist All good in the hood!"
echo "EA and EA Template Exist in both folders!"
else
echo "Errors! occurred please correct the below"
echo " Extension Attributes | Templates"
Expand Down
10 changes: 5 additions & 5 deletions tools/ci_tests/validatexml.sh
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/bin/bash
#!/bin/zsh
###################################################################################
## Validates XML for proper formatting
###################################################################################
Expand All @@ -9,18 +9,18 @@ function scripts() {
printf "\033[31m Working on Scripts\n"
printf "\033[31m---------------------------------------------------------------------------------\n"
printf "\033[0m"
scriptfolders=$(ls -ltr ./scripts | cut -c52- | awk 'NR>1')
scriptfolders=$(ls -1 ./scripts | grep -v templates)
while read folder ; do
echo "$folder"
xmllint --noout ./scripts/"$folder"/*.xml
xmllint --noout ./scripts/"$folder"/*.xml
done <<< "$scriptfolders"

}



function ea(){
eafolders=$(ls -ltr ./extension_attributes | cut -c52- | awk 'NR>1')
eafolders=$(ls -1 ./extension_attributes | grep -v templates)

printf "\033[31m---------------------------------------------------------------------------------\n"
printf "\033[31m Working on Extension Attributes\n"
Expand All @@ -30,7 +30,7 @@ function ea(){

echo "$folder"

xmllint --noout ./extension_attributes/"$folder"/*.xml
xmllint --noout ./extension_attributes/"$folder"/*.xml
done <<< "$eafolders"

}
Expand Down
Loading