@@ -23,22 +23,20 @@ class MattermostWebhookBody:
2323 todoColor = "#FFFFFF"
2424 watchColor = "#FFD700"
2525
26- def __init__ (self , username , icon , eventName , eventJsonStr , actionRepo ):
26+ def __init__ (self , username , icon , eventName , eventJson , actionRepo ):
2727 self .username = username
2828 self .icon = icon
2929 self .eventName = eventName .lower ()
30- self .eventJsonStr = eventJsonStr
30+ self .eventJson = eventJson
3131 self .actionRepo = actionRepo
32- self .eventJson = json .loads (eventJsonStr )
33- self .repoJson = self .eventJson ["repository" ]
34- self .senderJson = self .eventJson ["sender" ]
32+ self .event = json .loads (eventJson )
33+ self .repo = self .event ["repository" ]
34+ self .sender = self .event ["sender" ]
3535
3636 self .body = {
37- # "username": self.username,
38- # "icon_url": self.icon,
39- "username" : self .senderJson ['login' ],
40- "icon_url" : self .senderJson ['avatar_url' ],
41- "props" : {"card" : f"```json\n { self .eventJsonStr } \n ```" },
37+ "username" : self .sender ['login' ],
38+ "icon_url" : self .sender ['avatar_url' ],
39+ "props" : {"card" : f"```json\n { self .eventJson } \n ```" },
4240 }
4341
4442 # self.attachment = {
@@ -79,17 +77,17 @@ def __init__(self, username, icon, eventName, eventJsonStr, actionRepo):
7977 self .body ["attachments" ] = [self .attachment ]
8078
8179 def createTitle (self ):
82- login = self .senderJson ["login" ]
83- loginUrl = self .senderJson ["html_url" ]
84- repoName = self .repoJson ["full_name" ]
85- repoUrl = self .repoJson ["html_url" ]
80+ login = self .sender ["login" ]
81+ loginUrl = self .sender ["html_url" ]
82+ repoName = self .repo ["full_name" ]
83+ repoUrl = self .repo ["html_url" ]
8684 # starCount = self.repoJson["stargazers_count"]
8785 # starUrl = f"{repoUrl}/stargazers"
8886
8987 title = f"{ self .eventName .capitalize ().replace ('_' , ' ' )} "
9088
9189 try :
92- action = self .eventJson ["action" ]
90+ action = self .event ["action" ]
9391 title += f" { action } "
9492 except Exception :
9593 pass
@@ -99,23 +97,23 @@ def createTitle(self):
9997
10098 def addPushDetails (self ):
10199 self .body ["text" ] = self .createTitle ()
102- forced = self .eventJson ["forced" ]
103- commits = self .eventJson ["commits" ]
100+ forced = self .event ["forced" ]
101+ commits = self .event ["commits" ]
104102
105103 if forced :
106104 pushBody = "Force-pushed "
107105 else :
108106 pushBody = "Pushed "
109107
110- pushBody += f"[{ len (commits )} commit(s)]({ self .eventJson ['compare' ]} ) to { self .eventJson ['ref' ]} "
108+ pushBody += f"[{ len (commits )} commit(s)]({ self .event ['compare' ]} ) to { self .event ['ref' ]} "
111109 for c in commits :
112110 pushBody += f"\n [`{ c ['id' ][:6 ]} `]({ c ['url' ]} ) { c ['message' ]} "
113111 self .attachment ["color" ] = self .pushColor
114112 self .attachment ["text" ] = pushBody
115113
116114 def addPullRequestDetails (self ):
117115 self .body ["text" ] = self .createTitle ()
118- prJson = self .eventJson ["pull_request" ]
116+ prJson = self .event ["pull_request" ]
119117 headJson = prJson ["head" ]
120118 baseJson = prJson ["base" ]
121119 self .attachment ["color" ] = self .prColor
@@ -155,8 +153,8 @@ def addPullRequestDetails(self):
155153
156154 def addPullRequestReviewCommentDetails (self ):
157155 self .body ["text" ] = self .createTitle ()
158- commentJson = self .eventJson ["comment" ]
159- prJson = self .eventJson ['pull_request' ]
156+ commentJson = self .event ["comment" ]
157+ prJson = self .event ['pull_request' ]
160158 bodyTxt = f"[Comment]({ commentJson ['html_url' ]} ) in [PR#{ prJson ['number' ]} : { prJson ['title' ]} ]({ prJson ['html_url' ]} ):\n "
161159
162160 try :
@@ -169,9 +167,9 @@ def addPullRequestReviewCommentDetails(self):
169167
170168 def addPullRequestReviewDetails (self ):
171169 self .body ["text" ] = self .createTitle ()
172- reviewJson = self .eventJson ["review" ]
170+ reviewJson = self .event ["review" ]
173171 reviewState = reviewJson ['state' ]
174- prJson = self .eventJson ['pull_request' ]
172+ prJson = self .event ['pull_request' ]
175173 bodyTxt = f"[Review]({ reviewJson ['html_url' ]} ) of [PR#{ prJson ['number' ]} : { prJson ['title' ]} ]({ prJson ['html_url' ]} )\n "
176174 bodyTxt += f"Review State: { reviewState .capitalize ()} \n "
177175 bodyTxt += f"{ reviewJson ['body' ]} "
@@ -183,16 +181,16 @@ def addPullRequestReviewDetails(self):
183181
184182 def addDeleteDetails (self ):
185183 self .body ["text" ] = self .createTitle ()
186- self .attachment ["text" ] = f"Deleted { self .eventJson ['ref_type' ]} \" { self .eventJson ['ref' ]} \" "
184+ self .attachment ["text" ] = f"Deleted { self .event ['ref_type' ]} \" { self .event ['ref' ]} \" "
187185
188186 def addCreateDetails (self ):
189187 self .body ["text" ] = self .createTitle ()
190- self .attachment ["text" ] = f"Created { self .eventJson ['ref_type' ]} \" { self .eventJson ['ref' ]} \" "
188+ self .attachment ["text" ] = f"Created { self .event ['ref_type' ]} \" { self .event ['ref' ]} \" "
191189
192190 def addIssuesDetails (self ):
193191 self .body ["text" ] = self .createTitle ()
194- action = self .eventJson ["action" ]
195- issueJson = self .eventJson ["issue" ]
192+ action = self .event ["action" ]
193+ issueJson = self .event ["issue" ]
196194 issueTitle = issueJson ["title" ]
197195 issueUrl = issueJson ["html_url" ]
198196 issueBody = issueJson ["body" ]
@@ -217,10 +215,10 @@ def addIssuesDetails(self):
217215
218216 def addIssueCommentDetails (self ):
219217 self .body ["text" ] = self .createTitle ()
220- commentJson = self .eventJson ["comment" ]
218+ commentJson = self .event ["comment" ]
221219 commentBody = commentJson ["body" ]
222220 commentUrl = commentJson ["html_url" ]
223- issueJson = self .eventJson ["issue" ]
221+ issueJson = self .event ["issue" ]
224222 issueTitle = issueJson ["title" ]
225223 issueNumber = issueJson ["number" ]
226224
@@ -237,14 +235,14 @@ def addIssueCommentDetails(self):
237235
238236 def addForkDetails (self ):
239237 self .body ["text" ] = self .createTitle ()
240- forkeeJson = self .eventJson ["forkee" ]
238+ forkeeJson = self .event ["forkee" ]
241239 bodyText = f"Forkee [{ forkeeJson ['full_name' ]} ]({ forkeeJson ['html_url' ]} )"
242240 self .attachment ["text" ] = bodyText
243241
244242 def addReleaseDetails (self ):
245243 self .body ["text" ] = self .createTitle ()
246- action = self .eventJson ["action" ]
247- releaseJson = self .eventJson ["release" ]
244+ action = self .event ["action" ]
245+ releaseJson = self .event ["release" ]
248246 isDraft = releaseJson ["draft" ]
249247 isPrerelease = releaseJson ["prerelease" ]
250248
@@ -277,10 +275,10 @@ def addReleaseDetails(self):
277275
278276 def addWatchDetails (self ):
279277 self .body ["text" ] = f"{ self .createTitle ()} #stargazer"
280- login = self .senderJson ["login" ]
281- loginUrl = self .senderJson ["html_url" ]
282- userUrl = self .senderJson ["url" ]
283- starCount = self .repoJson ["stargazers_count" ]
278+ login = self .sender ["login" ]
279+ loginUrl = self .sender ["html_url" ]
280+ userUrl = self .sender ["url" ]
281+ starCount = self .repo ["stargazers_count" ]
284282
285283 bodyText = f"[{ login } ]({ loginUrl } ) is stargazer number { starCount } \n \n "
286284
@@ -344,7 +342,7 @@ def addWatchDetails(self):
344342 def addDefaultDetails (self ):
345343 self .attachment ["color" ] = self .todoColor
346344 self .attachment ["text" ] = self .createTitle ()
347- self .attachment ["fallback" ] = f"{ eventName .capitalize ().replace ('_' , ' ' )} by { self .senderJson ['login' ]} in { self .repoJson ['full_name' ]} "
345+ self .attachment ["fallback" ] = f"{ self . eventName .capitalize ().replace ('_' , ' ' )} by { self .sender ['login' ]} in { self .repo ['full_name' ]} "
348346
349347 def dumpJson (self ):
350348 return json .dumps (self .body )
@@ -353,16 +351,59 @@ def dumpJson(self):
353351if __name__ == '__main__' :
354352 url = os .getenv ("INPUT_WEBHOOKURL" )
355353
356- # Handle both base64-encoded and direct JSON input
357- eventJsonB64 = os .getenv ("INPUT_EVENTJSON_B64" )
358- if eventJsonB64 :
354+ # Handle event JSON provided inline; auto-detect if it's JSON or base64-encoded JSON
355+ def _try_parse_json (s : str ):
359356 try :
360- eventJsonStr = base64 .b64decode (eventJsonB64 ).decode ('utf-8' )
361- except Exception as e :
362- print (f"Failed to decode base64 event JSON: { e } " )
363- eventJsonStr = os .getenv ("INPUT_EVENTJSON" )
357+ json .loads (s )
358+ return True
359+ except Exception :
360+ return False
361+
362+ def _try_decode_b64_to_json_str (s : str ):
363+ if s is None :
364+ return None
365+ try :
366+ # strict validation first
367+ decoded = base64 .b64decode (s , validate = True )
368+ decoded_str = decoded .decode ('utf-8' )
369+ if _try_parse_json (decoded_str ):
370+ return decoded_str
371+ except Exception :
372+ # Try non-strict decode
373+ try :
374+ decoded = base64 .b64decode (s )
375+ decoded_str = decoded .decode ('utf-8' )
376+ if _try_parse_json (decoded_str ):
377+ return decoded_str
378+ except Exception :
379+ pass
380+ # As a last resort, try appending one to four '=' padding chars
381+ for i in range (1 , 5 ):
382+ try :
383+ s_padded = s + ("=" * i )
384+ decoded = base64 .b64decode (s_padded )
385+ decoded_str = decoded .decode ('utf-8' )
386+ if _try_parse_json (decoded_str ):
387+ return decoded_str
388+ except Exception :
389+ continue
390+ return None
391+
392+ eventInput = os .getenv ("INPUT_EVENTJSON" )
393+ eventJson = ""
394+
395+ if eventInput and _try_parse_json (eventInput ):
396+ eventJson = eventInput
397+ print ("Detected valid JSON in INPUT_EVENTJSON" )
364398 else :
365- eventJsonStr = os .getenv ("INPUT_EVENTJSON" )
399+ decoded = _try_decode_b64_to_json_str (eventInput )
400+ if decoded is not None :
401+ eventJson = decoded
402+ print ("Detected base64-encoded JSON in INPUT_EVENTJSON and decoded it" )
403+
404+ if not eventJson :
405+ print ("ERROR: No valid event JSON provided in INPUT_EVENTJSON" )
406+ exit (1 )
366407 username = os .getenv ("INPUT_SENDERUSERNAME" )
367408 icon = os .getenv ("INPUT_SENDERICONURL" )
368409 actionRepo = os .getenv ("GITHUB_ACTION_REPOSITORY" )
@@ -372,73 +413,62 @@ def dumpJson(self):
372413 os .environ ["ZITI_LOG" ] = zitiLogLevel
373414 os .environ ["TLSUV_DEBUG" ] = zitiLogLevel
374415
375- # Setup Ziti identity
376- zitiJwt = os .getenv ("INPUT_ZITIJWT" )
377- if zitiJwt is not None :
378- zitiId = openziti .enroll (zitiJwt )
416+ # Set up Ziti identity
417+ zitiJwtInput = os .getenv ("INPUT_ZITIJWT" )
418+ zitiIdJson = None # validated JSON string form
419+ zitiIdEncoding = None # validated base64 string form
420+ zitiIdContext = None # deserialized dict
421+ if zitiJwtInput is not None :
422+ # Expect enroll to return the identity JSON content
423+ try :
424+ enrolled = openziti .enroll (zitiJwtInput )
425+ # Validate that the returned content is JSON
426+ zitiIdContext = json .loads (enrolled )
427+ zitiIdJson = enrolled
428+ print ("Obtained valid identity JSON from INPUT_ZITIJWT enrollment" )
429+ except Exception as e :
430+ print (f"ERROR: Failed to enroll or parse identity from INPUT_ZITIJWT: { e } " )
431+ exit (1 )
379432 else :
380- zitiId = os .getenv ("INPUT_ZITIID" )
381-
382- if zitiId is None :
383- print ("ERROR: no Ziti identity provided, set INPUT_ZITIID or INPUT_ZITIJWT" )
384- exit (1 )
385-
386- def generate_json_schema (obj , max_depth = 10 , current_depth = 0 ):
387- """Generate a schema representation of a JSON object by inferring types from values."""
388- if current_depth >= max_depth :
389- return "<max_depth_reached>"
390-
391- if obj is None :
392- return "null"
393- elif isinstance (obj , bool ):
394- return "boolean"
395- elif isinstance (obj , int ):
396- return "integer"
397- elif isinstance (obj , float ):
398- return "number"
399- elif isinstance (obj , str ):
400- return "string"
401- elif isinstance (obj , list ):
402- if len (obj ) == 0 :
403- return "array[]"
404- # Get schema of first element as representative
405- element_schema = generate_json_schema (obj [0 ], max_depth , current_depth + 1 )
406- return f"array[{ element_schema } ]"
407- elif isinstance (obj , dict ):
408- schema = {}
409- for key , value in obj .items ():
410- schema [key ] = generate_json_schema (value , max_depth , current_depth + 1 )
411- return schema
433+ # Support inline JSON or base64-encoded identity JSON from a single variable
434+ zitiIdInput = os .getenv ("INPUT_ZITIID" )
435+
436+ # Prefer valid inline JSON if present
437+ if zitiIdInput and _try_parse_json (zitiIdInput ):
438+ zitiIdJson = zitiIdInput
439+ zitiIdContext = json .loads (zitiIdInput )
440+ print ("Detected valid inline JSON in INPUT_ZITIID" )
412441 else :
413- return f"unknown_type({ type (obj ).__name__ } )"
414-
415- # Accept only inline JSON for zitiId; do not interpret as file path or base64
442+ # Try decoding inline as base64 if provided and not valid JSON
443+ decodedInline = _try_decode_b64_to_json_str (zitiIdInput ) if zitiIdInput else None
444+ if decodedInline is not None :
445+ zitiIdEncoding = zitiIdInput
446+ zitiIdJson = decodedInline
447+ zitiIdContext = json .loads (decodedInline )
448+ print ("Detected base64-encoded identity in INPUT_ZITIID and decoded it" )
449+
450+ if zitiIdJson is None :
451+ print ("ERROR: no Ziti identity provided, set INPUT_ZITIID (inline JSON or base64-encoded), or INPUT_ZITIJWT" )
452+ exit (1 )
453+
454+ # Keep a small helper for safe string hints (used only in error/debug prints if needed)
416455 def _safe_hint (s ):
417456 if s is None :
418457 return "<none>"
419458 hint_len = len (s )
420459 head = s [:8 ].replace ('\n ' , ' ' )
421460 return f"len={ hint_len } , startswith='{ head } ...'"
422461
423- try :
424- zitiIdJson = json .loads (zitiId )
425- zitiIdContent = zitiId
426- except Exception as e :
427- print ("ERROR: zitiId must be inline JSON (not a file path or base64)." )
428- print (f"DEBUG: INPUT_ZITIID hint: { _safe_hint (zitiId )} " )
429- print (f"DEBUG: json error: { e } " )
430- exit (1 )
431-
432462 idFilename = "id.json"
433463 with open (idFilename , 'w' ) as f :
434- f .write (zitiIdContent )
464+ f .write (zitiIdJson )
435465
436466 # Defer openziti.load() until inside the monkeypatch context to keep
437467 # initialization/teardown paired and avoid double-free on shutdown.
438468
439469 # Create webhook body
440470 try :
441- mwb = MattermostWebhookBody (username , icon , eventName , eventJsonStr , actionRepo )
471+ mwb = MattermostWebhookBody (username , icon , eventName , eventJson , actionRepo )
442472 except Exception as e :
443473 print (f"Exception creating webhook body: { e } " )
444474 raise e
@@ -454,8 +484,8 @@ def _safe_hint(s):
454484 openziti .load (idFilename )
455485 except Exception as e :
456486 print (f"ERROR: Failed to load Ziti identity: { e } " )
457- schema = generate_json_schema ( zitiIdJson )
458- print (f"DEBUG: zitiId schema for troubleshooting: { json . dumps ( schema , indent = 2 ) } " )
487+ print ( f"DEBUG: INPUT_ZITIID hint: { _safe_hint ( os . getenv ( 'INPUT_ZITIID' )) } " )
488+ print (f"DEBUG: zitiIdJson len= { len ( zitiIdJson ) if zitiIdJson else 0 } " )
459489 raise e
460490
461491 session = None
0 commit comments