55import re
66import sys
77import datetime
8+ import hashlib
89from typing import Optional , Dict , Iterable
910
1011import requests
@@ -98,25 +99,50 @@ def find_pr() -> str:
9899 raise Exception (f"The { event_type } event doesn\' t relate to a Pull Request." )
99100
100101def current_user () -> str :
102+
103+ token_hash = hashlib .sha256 (os .environ ["GITHUB_TOKEN" ].encode ()).hexdigest ()
104+
105+ try :
106+ with open (f'.dflook-terraform/token-cache/{ token_hash } ' ) as f :
107+ username = f .read ()
108+ debug (f'GITHUB_TOKEN username: { username } ' )
109+ return username
110+ except Exception as e :
111+ debug (str (e ))
112+
101113 response = github_api_request ('get' , 'https://api.github.com/user' )
102114 if response .status_code != 403 :
103115 user = response .json ()
104116 debug ('GITHUB_TOKEN user:' )
105117 debug (json .dumps (user ))
106118
107- return user ['login' ]
119+ username = user ['login' ]
120+ else :
121+ # Assume this is the github actions app token
122+ username = 'github-actions[bot]'
108123
109- # Assume this is the github actions app token
110- return 'github-actions[bot]'
124+ try :
125+ os .makedirs ('.dflook-terraform/token-cache' , exist_ok = True )
126+ with open (f'.dflook-terraform/token-cache/{ token_hash } ' , 'w' ) as f :
127+ f .write (username )
128+ except Exception as e :
129+ debug (str (e ))
130+
131+ debug (f'GITHUB_TOKEN username: { username } ' )
132+ return username
111133
112134class TerraformComment :
113135 """
114136 The GitHub comment for this specific terraform plan
115137 """
116138
117- def __init__ (self , pr_url : str ):
139+ def __init__ (self , pr_url : str = None ):
118140 self ._plan = None
119141 self ._status = None
142+ self ._comment_url = None
143+
144+ if pr_url is None :
145+ return
120146
121147 response = github_api_request ('get' , pr_url )
122148 response .raise_for_status ()
@@ -125,20 +151,20 @@ def __init__(self, pr_url: str):
125151 response = github_api_request ('get' , self ._issue_url )
126152 response .raise_for_status ()
127153
128- self ._comment_url = None
154+ username = current_user ()
155+
129156 debug ('Looking for an existing comment:' )
130157 for comment in response .json ():
131158 debug (json .dumps (comment ))
132- if comment ['user' ]['login' ] == current_user () :
133- match = re .match (rf'{ re .escape (self ._comment_identifier )} \n ```(?:hcl)?(.*?)```(.*) ' , comment ['body' ], re .DOTALL )
159+ if comment ['user' ]['login' ] == username :
160+ match = re .match (rf'{ re .escape (self ._comment_identifier )} .* ```(?:hcl)?(.*?)```.* ' , comment ['body' ], re .DOTALL )
134161
135162 if not match :
136- match = re .match (rf'{ re .escape (self ._old_comment_identifier )} \n```(.*?)```(.*) ' , comment ['body' ], re .DOTALL )
163+ match = re .match (rf'{ re .escape (self ._old_comment_identifier )} \n```(.*?)```.* ' , comment ['body' ], re .DOTALL )
137164
138165 if match :
139166 self ._comment_url = comment ['url' ]
140167 self ._plan = match .group (1 ).strip ()
141- self ._status = match .group (2 ).strip ()
142168 return
143169
144170 @property
@@ -270,12 +296,64 @@ def status(self) -> Optional[str]:
270296 def status (self , status : str ) -> None :
271297 self ._status = status .strip ()
272298
273- def update_comment (self ):
299+ def body (self ) -> str :
274300 body = f'{ self ._comment_identifier } \n ```hcl\n { self .plan } \n ```'
275301
276302 if self .status :
277303 body += '\n ' + self .status
278304
305+ return body
306+
307+ def collapsable_body (self ) -> str :
308+
309+ try :
310+ collapse_threshold = int (os .environ ['TF_PLAN_COLLAPSE_LENGTH' ])
311+ except (ValueError , KeyError ):
312+ collapse_threshold = 10
313+
314+ open = ''
315+ highlighting = ''
316+
317+ if self .plan .startswith ('Error' ):
318+ open = ' open'
319+ elif 'Plan:' in self .plan :
320+ highlighting = 'hcl'
321+ num_lines = len (self .plan .splitlines ())
322+ if num_lines < collapse_threshold :
323+ open = ' open'
324+
325+ body = f'''{ self ._comment_identifier }
326+ <details{ open } >
327+ <summary>{ self .summary ()} </summary>
328+
329+ ```{ highlighting }
330+ { self .plan }
331+ ```
332+ </details>
333+ '''
334+
335+ if self .status :
336+ body += '\n ' + self .status
337+
338+ return body
339+
340+ def summary (self ) -> str :
341+ summary = None
342+
343+ for line in self .plan .splitlines ():
344+ if line .startswith ('No changes' ) or line .startswith ('Error' ):
345+ return line
346+
347+ if line .startswith ('Plan:' ):
348+ summary = line
349+
350+ if line .startswith ('Changes to Outputs' ):
351+ return summary + ' Changes to Outputs.'
352+
353+ return summary
354+
355+ def update_comment (self ):
356+ body = self .collapsable_body ()
279357 debug (body )
280358
281359 if self ._comment_url is None :
@@ -313,5 +391,6 @@ def update_comment(self):
313391 if tf_comment .plan is None :
314392 exit (1 )
315393 print (tf_comment .plan )
394+ exit (0 )
316395
317396 tf_comment .update_comment ()
0 commit comments