Skip to content

Commit 58c7d9b

Browse files
committed
checkpoint
1 parent b5a1a2e commit 58c7d9b

File tree

2 files changed

+128
-101
lines changed

2 files changed

+128
-101
lines changed

.github/workflows/zhook.yml

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,13 +72,10 @@ jobs:
7272
set -o pipefail
7373
set -o xtrace
7474
75-
# Base64 encode JSON to avoid whitespace issues in env file
76-
ENCODED_JSON=$(echo "${INPUT_EVENTJSON}" | base64 -w 0)
77-
7875
cat > /tmp/docker.env << EOF
7976
INPUT_ZITIID=${INPUT_ZITIID}
8077
INPUT_WEBHOOKURL=${INPUT_WEBHOOKURL}
81-
INPUT_EVENTJSON_B64=${ENCODED_JSON}
78+
INPUT_EVENTJSON=${INPUT_EVENTJSON}
8279
INPUT_SENDERUSERNAME=${INPUT_SENDERUSERNAME}
8380
INPUT_SENDERICONURL=${INPUT_SENDERICONURL}
8481
INPUT_ZITILOGLEVEL=${INPUT_ZITILOGLEVEL}

zhook.py

Lines changed: 127 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -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):
353351
if __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

Comments
 (0)