-
-
Notifications
You must be signed in to change notification settings - Fork 175
/
github_action.py
154 lines (123 loc) · 5.96 KB
/
github_action.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
import logging
import os
import sys
import traceback
from io import TextIOWrapper
from typing import Mapping, Any, Optional
from publish import logger
class GithubAction:
# GitHub Actions environment file variable names
# https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#environment-files
ENV_FILE_VAR_NAME = 'GITHUB_ENV'
PATH_FILE_VAR_NAME = 'GITHUB_PATH'
JOB_SUMMARY_FILE_VAR_NAME = 'GITHUB_STEP_SUMMARY'
def __init__(self, file: Optional[TextIOWrapper] = None):
if file is None:
file = sys.stdout
# pre Python 3.7, TextIOWrapper does not have reconfigure
if isinstance(file, TextIOWrapper) and hasattr(file, 'reconfigure'):
# ensure we have utf8 encoding, the default encoding of sys.stdout on Windows is cp1252
file.reconfigure(encoding='utf-8')
self._file: TextIOWrapper = file
def set_output(self, name: str, value: Any):
self._command(self._file, 'set-output', value, {'name': name})
def add_mask(self, value: str):
self._command(self._file, 'add-mask', value)
def stop_commands(self, end_token: str):
self._command(self._file, 'stop-commands', end_token)
def continue_commands(self, end_token: str):
self._command(self._file, end_token)
def save_state(self, name: str, value: Any):
self._command(self._file, 'save-state', value, {'name': name})
def group(self, title: str):
self._command(self._file, 'group', title)
def group_end(self, ):
self._command(self._file, 'endgroup')
def debug(self, message: str):
logger.debug(message)
self._command(self._file, 'debug', message)
def warning(self, message: str, file: Optional[str] = None, line: Optional[int] = None, column: Optional[int] = None):
logger.warning(message)
params = {}
if file is not None:
params.update(file=file)
if line is not None:
params.update(line=line)
if column is not None:
params.update(col=column)
self._command(self._file, 'warning', message, params)
def _exception(self, te: traceback.TracebackException):
def exception_str(te: traceback.TracebackException) -> str:
# we take the last line, which ends with a newline, that we strip
return list(te.format_exception_only())[-1].split('\n')[0]
self.error('{te}{caused}{context}'.format(
te=exception_str(te),
caused=f' caused by {exception_str(te.__cause__)}' if te.__cause__ else '',
context=f' while handling {exception_str(te.__context__)}' if te.__context__ else ''
), exception=None)
for lines in te.format(chain=False):
for line in lines.split('\n'):
if line:
logger.debug(line)
cause = te.__cause__
while cause:
self._exception(cause)
cause = cause.__cause__
context = te.__context__
while context:
self._exception(context)
context = context.__context__
def error(self,
message: str,
file: Optional[str] = None, line: Optional[int] = None, column: Optional[int] = None,
exception: Optional[BaseException] = None):
if exception:
self._exception(traceback.TracebackException.from_exception(exception))
else:
logger.error(message)
params = {}
if file is not None:
params.update(file=file)
if line is not None:
params.update(line=line)
if column is not None:
params.update(col=column)
self._command(self._file, 'error', message, params)
@staticmethod
def _command(file: TextIOWrapper, command: str, value: str = '', params: Optional[Mapping[str, Any]] = None):
# take first line of value if multiline
value = value.split('\n', 1)[0]
if params is None:
params = {}
params = ','.join([f'{key}={str(value)}'
for key, value in params.items()])
params = f' {params}' if params else ''
try:
file.write(f'::{command}{params}::{value}')
file.write(os.linesep)
except Exception as e:
logging.error(f'Failed to forward command {command} to GithubActions: {e}')
def add_to_env(self, var: str, val: str):
if '\n' in val:
# if this is really needed, implement it as describe here:
# https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#multiline-strings
raise ValueError('Multiline values not supported for environment variables')
self._append_to_file(f'{var}={val}\n', self.ENV_FILE_VAR_NAME)
def add_to_path(self, path: str):
self._append_to_file(f'{path}\n', self.PATH_FILE_VAR_NAME)
def add_to_job_summary(self, markdown: str):
self._append_to_file(markdown, self.JOB_SUMMARY_FILE_VAR_NAME)
def _append_to_file(self, content: str, env_file_var_name: str):
# appends content to an environment file denoted by an environment variable name
# https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#environment-files
filename = os.getenv(env_file_var_name)
if not filename:
self.warning(f'Cannot append to environment file {env_file_var_name} as it is not set. '
f'See https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#environment-files')
return
try:
with open(filename, 'a', encoding='utf-8') as file:
file.write(content)
except Exception as e:
self.warning(f'Failed to write to environment file {filename}: {str(e)}. '
f'See https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#environment-files')