@@ -151,45 +151,103 @@ def find_jamf_script(url, token, script_name, page = 0):
151151 raise Exception ("failed to find the script, please investigate!" )
152152
153153
154- #function to find a EA script using the filename as the script name
154+ #function to get all extension attributes using the new API
155155@logger .catch
156- def find_ea_script (ea_name ):
157- ea_script = requests .get (url = f"{ url } /JSSResource/computerextensionattributes/name/{ ea_name } " , auth = (username ,password ))
158- if ea_script .status_code == requests .codes .ok :
159- return ea_script .json ()['computer_extension_attribute' ]
160- elif ea_script .status_code == requests .codes .not_found :
161- logger .warning (f"Found no script with name: { ea_name } " )
162- return None
156+ def get_all_jamf_extension_attributes (url , token , eas = [], page = 0 ):
157+ header = {"Authorization" : f"Bearer { token } " }
158+ page_size = 50
159+ params = {"page" : page , "page-size" : page_size , "sort" : "name:asc" }
160+ ea_list = requests .get (url = f"{ url } /uapi/v1/computer-extension-attributes" , headers = header , params = params )
161+ if ea_list .status_code == requests .codes .ok :
162+ ea_list = ea_list .json ()
163+ logger .info (f"we got { len (ea_list ['results' ])+ page } of { ea_list ['totalCount' ]} EA results" )
164+ page += 1
165+ if (page * page_size ) < ea_list ['totalCount' ]:
166+ logger .info ("seems there's more EAs to grab" )
167+ eas .extend (ea_list ['results' ])
168+ return get_all_jamf_extension_attributes (url , token , eas , page )
169+ else :
170+ logger .info ("reached the end of our EA search" )
171+ eas .extend (ea_list ['results' ])
172+ logger .success (f"retrieved { len (eas )} total extension attributes" )
173+ return eas
163174 else :
164- logger .error ("encountered an error retriving the extension attribute, stopping" )
165- logger .error (ea_script .text )
166- raise Exception ("encountered an error retriving the extension attribute, stopping" )
175+ logger .error (f"status code: { ea_list .status_code } " )
176+ logger .error ("error retrieving extension attribute list" )
177+ logger .error (ea_list .text )
178+ raise Exception ("error retrieving extension attribute list" )
167179
168180
169- #function to create EA script
181+ #function to find a specific extension attribute by name using the new API
170182@logger .catch
171- def create_ea_script (payload , id ):
172- headers = {"Accept" : "text/xml" , "Content-Type" : "text/xml" }
173- ea_script = requests .post (url = f"{ url } /JSSResource/computerextensionattributes/id/{ id } " , json = payload , auth = (username ,password ))
174- if ea_script .status_code == requests .codes .ok :
175- return "success"
183+ def find_jamf_extension_attribute (url , token , ea_name , page = 0 ):
184+ header = {"Authorization" : f"Bearer { token } " }
185+ page_size = 50
186+ params = {"page" : page , "page-size" : page_size , "sort" : "name:asc" }
187+ ea_list = requests .get (url = f"{ url } /uapi/v1/computer-extension-attributes" , headers = header , params = params )
188+ if ea_list .status_code == requests .codes .ok :
189+ ea_list = ea_list .json ()
190+ logger .info (f"we have searched { len (ea_list ['results' ])+ page } of { ea_list ['totalCount' ]} EA results" )
191+ ea_search = jmespath .search (f"results[?name == '{ ea_name } ']" , ea_list )
192+ if len (ea_search ) == 1 :
193+ logger .info ('found the extension attribute, returning it' )
194+ return ea_search [0 ]
195+ elif len (ea_search ) == 0 and (page * page_size ) < ea_list ['totalCount' ]:
196+ logger .info ("couldn't find the EA in this page, seems there's more to look through" )
197+ return find_jamf_extension_attribute (url , token , ea_name , page + 1 )
198+ else :
199+ logger .info (f"did not find any extension attribute named { ea_name } " )
200+ return "n/a"
201+ else :
202+ logger .error (f"status code: { ea_list .status_code } " )
203+ logger .error ("error retrieving extension attribute list" )
204+ logger .error (ea_list .text )
205+ raise Exception ("failed to find the extension attribute, please investigate!" )
206+
207+
208+ #function to create a new extension attribute using the new API
209+ @logger .catch
210+ def create_jamf_extension_attribute (url , token , payload ):
211+ header = {"Authorization" : f"Bearer { token } " }
212+ ea_request = requests .post (url = f"{ url } /uapi/v1/computer-extension-attributes" , headers = header , json = payload )
213+ if ea_request .status_code == requests .codes .created :
214+ logger .success ("extension attribute created" )
215+ return True
216+ else :
217+ logger .warning ("failed to create the extension attribute" )
218+ logger .debug (f"status code for create: { ea_request .status_code } " )
219+ logger .warning (ea_request .text )
220+ return False
221+
222+
223+ #function to update an existing extension attribute using the new API
224+ @logger .catch
225+ def update_jamf_extension_attribute (url , token , payload ):
226+ header = {"Authorization" : f"Bearer { token } " }
227+ ea_request = requests .put (url = f"{ url } /uapi/v1/computer-extension-attributes/{ payload ['id' ]} " , headers = header , json = payload )
228+ if ea_request .status_code in [requests .codes .accepted , requests .codes .ok ]:
229+ logger .success ("extension attribute was updated successfully" )
230+ return True
176231 else :
177- logger .error ("encountered an error creating the extension attribute, stopping" )
178- logger .error (ea_script .text )
179- raise Exception ("encountered an error creating the extension attribute, stopping" )
232+ logger .warning ("failed to update the extension attribute" )
233+ logger .debug (f"status code for put: { ea_request .status_code } " )
234+ logger .warning (ea_request .text )
235+ return False
180236
181237
182- #function to update existin EA script
238+ #function to delete an extension attribute using the new API
183239@logger .catch
184- def update_ea_script (payload , id ):
185- headers = {"Accept" : "text/xml" , "Content-Type" : "text/xml" }
186- ea_script = requests .put (url = f"{ url } /JSSResource/computerextensionattributes/id/{ id } " , json = payload , auth = (username ,password ))
187- if ea_script .status_code == requests .codes .ok :
188- return "success"
240+ def delete_jamf_extension_attribute (url , token , id ):
241+ header = {"Authorization" : f"Bearer { token } " }
242+ ea_request = requests .delete (url = f"{ url } /uapi/v1/computer-extension-attributes/{ id } " , headers = header )
243+ if ea_request .status_code in [requests .codes .ok , requests .codes .accepted , requests .codes .no_content ]:
244+ logger .success ("extension attribute was deleted successfully" )
245+ return True
189246 else :
190- logger .error ("encountered an error creating the extension attribute, stopping" )
191- logger .error (ea_script .text )
192- raise Exception ("encountered an error creating the extension attribute, stopping" )
247+ logger .warning ("failed to delete the extension attribute" )
248+ logger .debug (f"status code for delete: { ea_request .status_code } " )
249+ logger .warning (ea_request .text )
250+ return False
193251
194252
195253#function to compare sripts and see if they have changed. If they haven't, no need to update it
@@ -209,11 +267,20 @@ def compare_scripts(new, old):
209267
210268#retrieves list of files given a folder path and the list of valid file extensions to look for
211269@logger .catch
212- def find_local_scripts (script_dir , script_extensions ):
270+ def find_local_scripts (script_dir , script_extensions , exclude_dir = None ):
213271 script_list = []
214272 logger .info (f"searching for files ending in { script_extensions } in { script_dir } " )
215273 for file_type in script_extensions :
216274 script_list .extend (glob .glob (f"{ script_dir } /**/*.{ file_type } " , recursive = True ))
275+
276+ # Filter out files from the exclude directory if specified
277+ if exclude_dir and exclude_dir != 'false' :
278+ original_count = len (script_list )
279+ script_list = [script for script in script_list if not script .startswith (exclude_dir )]
280+ excluded_count = original_count - len (script_list )
281+ if excluded_count > 0 :
282+ logger .info (f"excluded { excluded_count } files from EA script directory: { exclude_dir } " )
283+
217284 logger .info ("found these: " , script_dir )
218285 logger .info (script_list )
219286 return script_list
@@ -226,14 +293,14 @@ def get_script_name(script_path):
226293
227294
228295@logger .catch
229- def push_scripts ():
296+ def push_scripts (exclude_ea_dir = None ):
230297 #grab the token from jamf
231298 logger .info ('grabing the token from jamf' )
232299 token = get_jamf_token (url ,auth_type , username , password )
233300 logger .info ('checking the list of local scripts to upload or create' )
234301 scripts = {}
235302 #this retrives the full path of the scripts we're trying to sync from github
236- scripts ['github' ] = find_local_scripts (script_dir , script_extensions )
303+ scripts ['github' ] = find_local_scripts (script_dir , script_extensions , exclude_ea_dir )
237304 #I need to simplify this array down to the just the name of the script, stripping out the path.
238305 scripts ['github_simple_name' ] = []
239306 for script in scripts ['github' ]:
@@ -304,8 +371,121 @@ def push_scripts():
304371 logger .success ("finished with the scripts" )
305372
306373
374+ @logger .catch
307375def push_ea_scripts ():
308- return ""
376+ if ea_script_dir == 'false' :
377+ logger .warning ("EA script directory not set, skipping EA script processing" )
378+ return
379+
380+ logger .info ('starting EA script processing' )
381+ #grab the token from jamf
382+ logger .info ('grabbing the token from jamf for EA scripts' )
383+ token = get_jamf_token (url , auth_type , username , password )
384+ logger .info ('checking the list of local EA scripts to upload or create' )
385+ ea_scripts = {}
386+
387+ #this retrieves the full path of the EA scripts we're trying to sync from github
388+ ea_scripts ['github' ] = find_local_scripts (ea_script_dir , script_extensions )
389+
390+ #I need to simplify this array down to just the name of the script, stripping out the path.
391+ ea_scripts ['github_simple_name' ] = []
392+ for ea_script in ea_scripts ['github' ]:
393+ ea_scripts ['github_simple_name' ].append (get_script_name (ea_script ).lower ())
394+
395+ logger .info ('double-checking for duplicate EA script names' )
396+ for count , ea_script in enumerate (ea_scripts ['github_simple_name' ]):
397+ if ea_scripts ['github_simple_name' ].count (ea_script ) >= 2 :
398+ logger .error (f"the EA script name { ea_script } is duplicated { ea_scripts ['github_simple_name' ].count (ea_script )} times, please give it a unique name" )
399+ sys .exit (1 )
400+
401+ #continue if no dupes are found
402+ logger .success ("nice, no duplicate EA script names, we can continue" )
403+ logger .info ('now checking jamf for its list of extension attributes' )
404+ ea_scripts ['jamf' ] = get_all_jamf_extension_attributes (url , token )
405+ logger .info ("setting all EA names to lower case to avoid false positives in our search." )
406+ logger .info ("worry not, this won't affect the actual naming :)" )
407+
408+ #save the EA names all in lower_case
409+ for ea_script in ea_scripts ['jamf' ]:
410+ ea_script ['lower_case_name' ] = ea_script ['name' ].lower ()
411+
412+ #make a copy of the jamf EAs, we'll use this to determine which to delete later on
413+ ea_scripts ['to_delete' ] = ea_scripts ['jamf' ]
414+
415+ logger .info ("processing each EA script now" )
416+ for count , ea_script in enumerate (ea_scripts ['github' ]):
417+ logger .info ("----------------------" )
418+ logger .info (f"EA script { count + 1 } of { len (ea_scripts ['github' ])} " )
419+ logger .info (f"path of the EA script: { ea_script } " )
420+ ea_script_name = get_script_name (ea_script )
421+
422+ if enable_prefix == "false" :
423+ #don't use the prefix
424+ logger .info (f"EA script name is: { ea_script_name } " )
425+ else :
426+ #use the branch name as prefix
427+ prefix = branch .split ('/' )[- 1 ]
428+ ea_script_name = f"{ prefix } _{ ea_script_name } "
429+ logger .info (f"the new EA script name: { ea_script_name } " )
430+
431+ #check to see if the EA script name exists in jamf
432+ logger .info (f"now let's see if { ea_script_name } exists in jamf already" )
433+ ea_search = jmespath .search (f"[?lower_case_name == '{ ea_script_name .lower ()} ']" , ea_scripts ['jamf' ])
434+
435+ if len (ea_search ) == 0 :
436+ logger .info ("it doesn't exist, lets create it" )
437+ #it doesn't exist, we can create it
438+ with open (ea_script , 'r' ) as upload_ea_script :
439+ ea_script_content = upload_ea_script .read ()
440+ payload = {
441+ "name" : ea_script_name ,
442+ "enabled" : True ,
443+ "description" : "Extension attribute script created via git2jamf" ,
444+ "dataType" : "String" ,
445+ "inputType" : {
446+ "type" : "Script" ,
447+ "platform" : "Mac" ,
448+ "script" : ea_script_content
449+ },
450+ "inventoryDisplay" : "General" ,
451+ "reconDisplay" : "Extension Attributes"
452+ }
453+ create_jamf_extension_attribute (url , token , payload )
454+
455+ elif len (ea_search ) == 1 :
456+ jamf_ea = ea_search .pop ()
457+ del jamf_ea ['lower_case_name' ]
458+ ea_scripts ['to_delete' ].remove (jamf_ea )
459+ logger .info ("it does exist, lets compare them" )
460+ #it does exist, lets see if it has changed
461+ with open (ea_script , 'r' ) as upload_ea_script :
462+ ea_script_content = upload_ea_script .read ()
463+ # Check if the script content is different
464+ current_script = ""
465+ if 'inputType' in jamf_ea and 'script' in jamf_ea ['inputType' ]:
466+ current_script = jamf_ea ['inputType' ]['script' ]
467+
468+ if not compare_scripts (ea_script_content , current_script ):
469+ logger .info ("the local EA version is different than the one in jamf, updating jamf" )
470+ #the hash of the scripts is not the same, so we'll update it
471+ jamf_ea ['inputType' ]['script' ] = ea_script_content
472+ update_jamf_extension_attribute (url , token , jamf_ea )
473+ else :
474+ logger .info ("we're skipping this EA script." )
475+
476+ if delete == 'true' :
477+ logger .warning (f"we have { len (ea_scripts ['to_delete' ])} extension attributes left to delete" )
478+ for ea_script in ea_scripts ['to_delete' ]:
479+ # Only delete extension attributes that have scripts (not other types like text input, etc.)
480+ if 'inputType' in ea_script and ea_script ['inputType' ].get ('type' , '' ).lower () == 'script' :
481+ logger .info (f"attempting to delete extension attribute { ea_script ['name' ]} in jamf" )
482+ delete_jamf_extension_attribute (url , token , ea_script ['id' ])
483+ else :
484+ logger .info (f"skipping deletion of non-script extension attribute: { ea_script ['name' ]} " )
485+
486+ logger .info ("expiring the token so it can't be used further" )
487+ invalidate_jamf_token (url , token )
488+ logger .success ("finished with the EA scripts" )
309489
310490
311491#run this thing
@@ -325,6 +505,9 @@ def push_ea_scripts():
325505 workspace_dir = os .getenv ('GITHUB_WORKSPACE' )
326506 if script_dir != workspace_dir :
327507 script_dir = f"{ workspace_dir } /{ script_dir } "
508+ # Process EA script directory path if it's set
509+ if ea_script_dir != 'false' and ea_script_dir != workspace_dir :
510+ ea_script_dir = f"{ workspace_dir } /{ ea_script_dir } "
328511 enable_prefix = os .getenv ('INPUT_PREFIX' )
329512 branch = os .getenv ('GITHUB_REF' )
330513 script_extensions = os .getenv ('INPUT_SCRIPT_EXTENSIONS' )
@@ -333,6 +516,7 @@ def push_ea_scripts():
333516 logger .info (f"url is: { url } " )
334517 logger .info (f"workspace dir is: { workspace_dir } " )
335518 logger .info (f"script_dir is: { script_dir } " )
519+ logger .info (f"ea_script_dir is: { ea_script_dir } " )
336520 logger .info (f"branch is set to: { branch } " )
337521 logger .info (f"script_deletion is: { delete } " )
338522 logger .info (f"scripts_extensions are: { script_extensions } " )
@@ -341,7 +525,7 @@ def push_ea_scripts():
341525 else :
342526 logger .warning (f"prefix enabled, using: { branch .split ('/' )[- 1 ]} " )
343527 #run the block to push the "normal" scripts to jamf
344- push_scripts ()
528+ push_scripts (ea_script_dir if ea_script_dir != 'false' else None )
345529 #check to see if we have an EA scripts to push over
346530 if ea_script_dir != 'false' :
347531 logger .info ("we have some EA scripts to process" )
0 commit comments