diff --git a/.gitignore b/.gitignore index 9ba664b..fe6f56c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ action/test .env .DS_Store -*.code-workspace \ No newline at end of file +*.code-workspace +__pycache__ \ No newline at end of file diff --git a/README.md b/README.md index 1b2c608..a111f89 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,20 @@ # git2jamf [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) -This action grabs the github repository (or any subdfolder of your choice) scans it for scripts and will create or update those scripts in jamf. +This action grabs the github repository (or any subfolder of your choice) scans it for scripts and will create or update those scripts in jamf. It starts by comparing filename of the github script (without the extension) against the name of the script in jamf: -* If it doesn't exist, it will create it -* if it exists, it will compare the hash of the body of both scripts and update it in jamf if they differ. Github is always treated as the source. -* If enabled, it will add a prefix with the `branch name_` to a script. +* If it doesn't exist, it will create it with a timestamped note indicating when it was created +* If it exists, it will compare the hash of the body of both scripts and update it in jamf if they differ. When updating, it will also update the notes with a timestamp of when the script was last updated, preserving any existing custom notes +* If enabled, it will add a prefix with the `branch name_` to a script. After creating and updating scripts, if enabled, it can delete any leftover script that is not found in github, thus keeping Github as your one source. +## Notes Management +The action automatically manages notes in Jamf scripts: +- **New scripts**: Get a "created via github action on [timestamp]" note +- **Updated scripts**: Get an "updated via github action on [timestamp]" note added/updated +- **Existing notes**: Custom notes are preserved and GitHub action timestamps are kept at the top +- **Order**: Created timestamp (if present) appears first, followed by updated timestamp, then any custom notes + ## Future state * handle extension attributes. * slack notifications diff --git a/action.py b/action.py index 8e39d8e..72dc706 100644 --- a/action.py +++ b/action.py @@ -7,6 +7,7 @@ import jmespath import hashlib import sys +from datetime import datetime, timezone from loguru import logger logger.remove() @@ -207,6 +208,53 @@ def compare_scripts(new, old): return False +#function to create or update notes with proper timestamping +@logger.catch +def update_script_notes(existing_notes, action_type="updated"): + timestamp = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S UTC") + commit_hash = os.getenv('GITHUB_SHA', 'unknown')[:7] # Get first 7 characters of commit hash + action_line = f"{action_type} via github action on {timestamp} (commit: {commit_hash})" + + if not existing_notes: + # No existing notes, just add the action line + return action_line + + lines = existing_notes.strip().split('\n') + updated_lines = [] + found_created_line = False + found_updated_line = False + + # Look for existing github action lines + for line in lines: + line = line.strip() + if line.startswith("created via github action"): + if not found_created_line: + updated_lines.append(line) # Keep the original created line + found_created_line = True + # Skip duplicate created lines + elif line.startswith("updated via github action"): + if not found_updated_line: + updated_lines.append(action_line) # Replace with new updated line + found_updated_line = True + # Skip old updated lines + else: + # Keep other notes + if line: # Only add non-empty lines + updated_lines.append(line) + + # If we didn't find an existing updated line, add it after created line (if exists) or at the top + if not found_updated_line: + if found_created_line: + # Insert after the created line + insert_index = 1 if len(updated_lines) > 0 else 0 + updated_lines.insert(insert_index, action_line) + else: + # Insert at the beginning + updated_lines.insert(0, action_line) + + return '\n'.join(updated_lines) + + #retrieves list of files given a folder path and the list of valid file extensions to look for @logger.catch def find_local_scripts(script_dir, script_extensions): @@ -276,7 +324,8 @@ def push_scripts(): logger.info("it doesn't exist, lets create it") #it doesn't exist, we can create it with open(script, 'r') as upload_script: - payload = {"name": script_name, "info": "", "notes": "created via github action", "priority": "AFTER" , "categoryId": "1", "categoryName":"", "parameter4":"", "parameter5":"", "parameter6":"", "parameter7":"", "parameter8":"", "parameter9":"", "parameter10":"", "parameter11":"", "osRequirements":"", "scriptContents":f"{upload_script.read()}"} + creation_note = update_script_notes("", "created") + payload = {"name": script_name, "info": "", "notes": creation_note, "priority": "AFTER" , "categoryId": "1", "categoryName":"", "parameter4":"", "parameter5":"", "parameter6":"", "parameter7":"", "parameter8":"", "parameter9":"", "parameter10":"", "parameter11":"", "osRequirements":"", "scriptContents":f"{upload_script.read()}"} create_jamf_script(url, token, payload) elif len(script_search) == 1: jamf_script = script_search.pop() @@ -290,6 +339,9 @@ def push_scripts(): logger.info("the local version is different than the one in jamf, updating jamf") #the hash of the scripts is not the same, so we'll update it jamf_script['scriptContents'] = script_text + # Update the notes with timestamp + existing_notes = jamf_script.get('notes', '') + jamf_script['notes'] = update_script_notes(existing_notes, "updated") update_jamf_script(url, token, jamf_script) else: logger.info("we're skipping this one.")