actions-tools 0.2.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- actions/__init__.py +12 -0
- actions/context.py +50 -0
- actions/core.py +380 -0
- actions/py.typed +0 -0
- actions_tools-0.2.1.dist-info/METADATA +296 -0
- actions_tools-0.2.1.dist-info/RECORD +9 -0
- actions_tools-0.2.1.dist-info/WHEEL +5 -0
- actions_tools-0.2.1.dist-info/licenses/LICENSE +674 -0
- actions_tools-0.2.1.dist-info/top_level.txt +1 -0
actions/__init__.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import re
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def get_version() -> str:
|
|
6
|
+
version = os.environ.get("GITHUB_REF_NAME", "0.0.1")
|
|
7
|
+
pattern = r"^\d+\.\d+\.\d+(?:[abc]\d*)?$"
|
|
8
|
+
match = re.match(pattern, version)
|
|
9
|
+
return version if match else "0.0.1"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
__version__ = get_version()
|
actions/context.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
action: str = os.environ.get("GITHUB_ACTION", "")
|
|
5
|
+
action_ref: str = os.environ.get("GITHUB_ACTION_REF", "")
|
|
6
|
+
action_repository: str = os.environ.get("GITHUB_ACTION_REPOSITORY", "")
|
|
7
|
+
actions: str = os.environ.get("GITHUB_ACTIONS", "")
|
|
8
|
+
actor: str = os.environ.get("GITHUB_ACTOR", "")
|
|
9
|
+
actor_id: str = os.environ.get("GITHUB_ACTOR_ID", "")
|
|
10
|
+
api_url: str = os.environ.get("GITHUB_API_URL", "")
|
|
11
|
+
base_ref: str = os.environ.get("GITHUB_BASE_REF", "")
|
|
12
|
+
env: str = os.environ.get("GITHUB_ENV", "")
|
|
13
|
+
event_name: str = os.environ.get("GITHUB_EVENT_NAME", "")
|
|
14
|
+
event_path: str = os.environ.get("GITHUB_EVENT_PATH", "")
|
|
15
|
+
graphql_url: str = os.environ.get("GITHUB_GRAPHQL_URL", "")
|
|
16
|
+
head_ref: str = os.environ.get("GITHUB_HEAD_REF", "")
|
|
17
|
+
job: str = os.environ.get("GITHUB_JOB", "")
|
|
18
|
+
output: str = os.environ.get("GITHUB_OUTPUT", "")
|
|
19
|
+
path: str = os.environ.get("GITHUB_PATH", "")
|
|
20
|
+
ref: str = os.environ.get("GITHUB_REF", "")
|
|
21
|
+
ref_name: str = os.environ.get("GITHUB_REF_NAME", "")
|
|
22
|
+
ref_protected: str = os.environ.get("GITHUB_REF_PROTECTED", "")
|
|
23
|
+
ref_type: str = os.environ.get("GITHUB_REF_TYPE", "")
|
|
24
|
+
repository: str = os.environ.get("GITHUB_REPOSITORY", "")
|
|
25
|
+
repository_id: str = os.environ.get("GITHUB_REPOSITORY_ID", "")
|
|
26
|
+
repository_owner: str = os.environ.get("GITHUB_REPOSITORY_OWNER", "")
|
|
27
|
+
repository_owner_id: str = os.environ.get("GITHUB_REPOSITORY_OWNER_ID", "")
|
|
28
|
+
retention_days: str = os.environ.get("GITHUB_RETENTION_DAYS", "")
|
|
29
|
+
run_attempt: str = os.environ.get("GITHUB_RUN_ATTEMPT", "")
|
|
30
|
+
run_id: str = os.environ.get("GITHUB_RUN_ID", "")
|
|
31
|
+
run_number: str = os.environ.get("GITHUB_RUN_NUMBER", "")
|
|
32
|
+
server_url: str = os.environ.get("GITHUB_SERVER_URL", "")
|
|
33
|
+
sha: str = os.environ.get("GITHUB_SHA", "")
|
|
34
|
+
step_summary: str = os.environ.get("GITHUB_STEP_SUMMARY", "")
|
|
35
|
+
triggering_actor: str = os.environ.get("GITHUB_TRIGGERING_ACTOR", "")
|
|
36
|
+
workflow: str = os.environ.get("GITHUB_WORKFLOW", "")
|
|
37
|
+
workflow_ref: str = os.environ.get("GITHUB_WORKFLOW_REF", "")
|
|
38
|
+
workflow_sha: str = os.environ.get("GITHUB_WORKFLOW_SHA", "")
|
|
39
|
+
workspace: str = os.environ.get("GITHUB_WORKSPACE", "")
|
|
40
|
+
|
|
41
|
+
runner_arch: str = os.environ.get("RUNNER_ARCH", "")
|
|
42
|
+
runner_debug: str = os.environ.get("RUNNER_DEBUG", "")
|
|
43
|
+
runner_environment: str = os.environ.get("RUNNER_ENVIRONMENT", "")
|
|
44
|
+
runner_name: str = os.environ.get("RUNNER_NAME", "")
|
|
45
|
+
runner_os: str = os.environ.get("RUNNER_OS", "")
|
|
46
|
+
runner_temp: str = os.environ.get("RUNNER_TEMP", "")
|
|
47
|
+
runner_tool_cache: str = os.environ.get("RUNNER_TOOL_CACHE", "")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
repository_name: str = os.environ.get("GITHUB_REPOSITORY", "/").split("/")[1]
|
actions/core.py
ADDED
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
import re
|
|
4
|
+
import secrets
|
|
5
|
+
import string
|
|
6
|
+
from contextlib import contextmanager
|
|
7
|
+
from typing import Any, List, Optional
|
|
8
|
+
from urllib.parse import quote
|
|
9
|
+
from urllib.request import Request, urlopen
|
|
10
|
+
|
|
11
|
+
from yaml import Loader, YAMLError, load
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
try:
|
|
15
|
+
# noinspection PyUnresolvedReferences
|
|
16
|
+
from github import Auth, Github
|
|
17
|
+
except ImportError: # pragma: no cover
|
|
18
|
+
_has_github = False
|
|
19
|
+
else:
|
|
20
|
+
_has_github = True
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-commands
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
_true = ["y", "yes", "true", "on"]
|
|
27
|
+
_false = ["n", "no", "false", "off"]
|
|
28
|
+
|
|
29
|
+
_indent = 0
|
|
30
|
+
_end_token = ""
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# Core
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def debug(message: str, **kwargs):
|
|
37
|
+
"""
|
|
38
|
+
Send a Debug message
|
|
39
|
+
https://docs.github.com/en/actions/how-tos/monitor-workflows/enable-debug-logging
|
|
40
|
+
:param message: Message
|
|
41
|
+
:param kwargs: Extra print kwargs
|
|
42
|
+
"""
|
|
43
|
+
print(f"::debug::{message}", **kwargs)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def info(message: str, **kwargs):
|
|
47
|
+
"""
|
|
48
|
+
Send an Info message
|
|
49
|
+
:param message: Message
|
|
50
|
+
:param kwargs: Extra print kwargs
|
|
51
|
+
"""
|
|
52
|
+
print(" " * _indent + message, **kwargs)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def notice(message: str, **kwargs):
|
|
56
|
+
"""
|
|
57
|
+
Send a Notice message
|
|
58
|
+
https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-commands#setting-a-notice-message
|
|
59
|
+
:param message: Message
|
|
60
|
+
:param kwargs: Notice options and print kwargs
|
|
61
|
+
"""
|
|
62
|
+
cmd_args = _cmd_args(kwargs)
|
|
63
|
+
print(f"::notice{cmd_args}::{message}", **kwargs)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def warn(message: str, **kwargs):
|
|
67
|
+
"""
|
|
68
|
+
Send a Warning message
|
|
69
|
+
https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-commands#setting-a-warning-message
|
|
70
|
+
:param message: Message
|
|
71
|
+
:param kwargs: Warning options and print kwargs
|
|
72
|
+
"""
|
|
73
|
+
cmd_args = _cmd_args(kwargs)
|
|
74
|
+
print(f"::warning{cmd_args}::{message}", **kwargs)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def error(message: str, **kwargs):
|
|
78
|
+
"""
|
|
79
|
+
Send an Error message
|
|
80
|
+
https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-commands#setting-an-error-message
|
|
81
|
+
:param message: Message
|
|
82
|
+
:param kwargs: Error options and print kwargs
|
|
83
|
+
"""
|
|
84
|
+
cmd_args = _cmd_args(kwargs)
|
|
85
|
+
print(f"::error{cmd_args}::{message}")
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _cmd_args(kwargs) -> str:
|
|
89
|
+
keys = ["title", "file", "col", "endColumn", "line", "endLine"]
|
|
90
|
+
results = []
|
|
91
|
+
items = kwargs.copy()
|
|
92
|
+
for key, value in items.items():
|
|
93
|
+
if key not in keys:
|
|
94
|
+
continue
|
|
95
|
+
value = str(value).replace(":", "%3A").replace(",", "%2C")
|
|
96
|
+
results.append(f"{key}={value}")
|
|
97
|
+
del kwargs[key]
|
|
98
|
+
result = ",".join(results)
|
|
99
|
+
return f" {result}" if result else ""
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def is_debug() -> bool:
|
|
103
|
+
return bool(os.getenv("RUNNER_DEBUG"))
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def set_failed(message: str):
|
|
107
|
+
error(message)
|
|
108
|
+
raise SystemExit
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def mask(message: str):
|
|
112
|
+
"""
|
|
113
|
+
Mask a secret
|
|
114
|
+
https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-commands#masking-a-value-in-a-log
|
|
115
|
+
:param message: Secret to mask
|
|
116
|
+
"""
|
|
117
|
+
print(f"::add-mask::{message}")
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def start_group(title: str):
|
|
121
|
+
print(f"::group::{title}")
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def end_group():
|
|
125
|
+
print("::endgroup::")
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
@contextmanager
|
|
129
|
+
def group(title: str):
|
|
130
|
+
print(f"::group::{title}")
|
|
131
|
+
try:
|
|
132
|
+
yield info
|
|
133
|
+
finally:
|
|
134
|
+
print("::endgroup::")
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def stop_commands(end_token: str = ""):
|
|
138
|
+
global _end_token
|
|
139
|
+
if not end_token:
|
|
140
|
+
r = get_random(16)
|
|
141
|
+
end_token = "".join(r)
|
|
142
|
+
_end_token = end_token
|
|
143
|
+
print(f"::stop-commands::{_end_token}")
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def start_commands(end_token: str = ""):
|
|
147
|
+
if not end_token:
|
|
148
|
+
end_token = _end_token
|
|
149
|
+
print(f"::{end_token}::")
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def set_output(output: str, value: Any):
|
|
153
|
+
with open(os.environ["GITHUB_OUTPUT"], "a") as f:
|
|
154
|
+
print(f"{output}={value}", file=f) # type: ignore
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def set_env(name: str, value: str):
|
|
158
|
+
with open(os.environ["GITHUB_ENV"], "a") as f:
|
|
159
|
+
print(f"{name}={value}", file=f) # type: ignore
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def add_path(path: str):
|
|
163
|
+
with open(os.environ["GITHUB_PATH"], "a") as f:
|
|
164
|
+
print(path, file=f) # type: ignore
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def set_state(name: str, value: Any) -> str:
|
|
168
|
+
if name.startswith("STATE_"):
|
|
169
|
+
name = name[6:]
|
|
170
|
+
with open(os.environ["GITHUB_STATE"], "a") as f:
|
|
171
|
+
print(f"{name}={value}", file=f) # type: ignore
|
|
172
|
+
return f"STATE_{name}"
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def get_state(name: str) -> str:
|
|
176
|
+
if name.startswith("STATE_"):
|
|
177
|
+
name = name[6:]
|
|
178
|
+
return os.getenv(f"STATE_{name}", "")
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def summary(text: str, nlc: int = 1):
|
|
182
|
+
"""
|
|
183
|
+
Write to the Job Summary file
|
|
184
|
+
https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-commands#adding-a-job-summary
|
|
185
|
+
:param text:str: Raw Text
|
|
186
|
+
:param nlc:int: New Line Count
|
|
187
|
+
:return:
|
|
188
|
+
"""
|
|
189
|
+
new_lines = os.linesep * nlc
|
|
190
|
+
with open(os.environ["GITHUB_STEP_SUMMARY"], "a") as f:
|
|
191
|
+
print(f"{text}{new_lines}", file=f) # type: ignore
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
# Inputs
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def get_input(name: str, req: bool = False, strip: bool = True) -> str:
|
|
198
|
+
"""
|
|
199
|
+
Get String Input
|
|
200
|
+
:param name: str: Input Name
|
|
201
|
+
:param req: bool: If Required
|
|
202
|
+
:param strip: bool: To Strip
|
|
203
|
+
:return:
|
|
204
|
+
"""
|
|
205
|
+
value: str = _get_input_str(name, strip)
|
|
206
|
+
if req and not value:
|
|
207
|
+
raise ValueError(f"Error Parsing Required Input: {name} -> {value}")
|
|
208
|
+
return value
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def get_bool(name: str, req: bool = False) -> bool:
|
|
212
|
+
"""
|
|
213
|
+
Get Boolean Input
|
|
214
|
+
:param name: str: Input Name
|
|
215
|
+
:param req: bool: If Required
|
|
216
|
+
:return:
|
|
217
|
+
"""
|
|
218
|
+
value = _get_input_str(name, True).lower()
|
|
219
|
+
if req and value not in _true + _false:
|
|
220
|
+
raise ValueError(f"Error Parsing Required Input: {name} -> {value}")
|
|
221
|
+
if value in _true:
|
|
222
|
+
return True
|
|
223
|
+
return False
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def get_list(name: str, req: bool = False, strip: bool = True, split: str = "[,|\n]") -> List[str]:
|
|
227
|
+
"""
|
|
228
|
+
Get List Input
|
|
229
|
+
:param name: str: Input Name
|
|
230
|
+
:param req: bool: If Required
|
|
231
|
+
:param strip: bool: To Strip
|
|
232
|
+
:param split: str: Split Regex
|
|
233
|
+
:return:
|
|
234
|
+
"""
|
|
235
|
+
value = _get_input_str(name, True)
|
|
236
|
+
results: List[str] = []
|
|
237
|
+
for x in re.split(split, value):
|
|
238
|
+
if strip:
|
|
239
|
+
x = x.strip()
|
|
240
|
+
if x:
|
|
241
|
+
results.append(x)
|
|
242
|
+
if req and not results:
|
|
243
|
+
raise ValueError(f"Error Parsing Required Input: {name} -> {value}")
|
|
244
|
+
return results
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def get_data(name: str, req=False) -> Any:
|
|
248
|
+
"""
|
|
249
|
+
Get Data Input - from JSON or YAML String
|
|
250
|
+
Parse Order - json.loads -> yaml.load -> None
|
|
251
|
+
:param name: str: Input Name
|
|
252
|
+
:param req: bool: If Required
|
|
253
|
+
:return:
|
|
254
|
+
"""
|
|
255
|
+
value = _get_input_str(name, True)
|
|
256
|
+
try:
|
|
257
|
+
return json.loads(value)
|
|
258
|
+
except json.JSONDecodeError:
|
|
259
|
+
pass
|
|
260
|
+
try:
|
|
261
|
+
res = load(value, Loader=Loader)
|
|
262
|
+
if res:
|
|
263
|
+
return res
|
|
264
|
+
except YAMLError:
|
|
265
|
+
pass
|
|
266
|
+
if req:
|
|
267
|
+
raise ValueError(f"Error Parsing Required Input: {name} -> {repr(value)}")
|
|
268
|
+
return None
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def get_dict(name: str, req=False) -> dict:
|
|
272
|
+
"""
|
|
273
|
+
Get Dict Input - from JSON or YAML String
|
|
274
|
+
Same as get_data except always returns a dict
|
|
275
|
+
:param name: str: Input Name
|
|
276
|
+
:param req: bool: If Required
|
|
277
|
+
:return:
|
|
278
|
+
"""
|
|
279
|
+
results = get_data(name, req)
|
|
280
|
+
if not isinstance(results, dict):
|
|
281
|
+
if req:
|
|
282
|
+
raise ValueError(f"Error Parsing Input as Dict: {name}")
|
|
283
|
+
return {}
|
|
284
|
+
return results
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def _get_input_str(name: str, strip: bool = True) -> str:
|
|
288
|
+
value = os.getenv(f"INPUT_{name.upper()}", "")
|
|
289
|
+
if strip:
|
|
290
|
+
value = value.strip()
|
|
291
|
+
return value
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
# OIDC
|
|
295
|
+
# https://docs.github.com/en/actions/reference/security/oidc#methods-for-requesting-the-oidc-token
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def get_id_token(audience: Optional[str] = None) -> str:
|
|
299
|
+
"""
|
|
300
|
+
Get and mask OIDC Token
|
|
301
|
+
:param audience:
|
|
302
|
+
:return:
|
|
303
|
+
"""
|
|
304
|
+
tip = "Check permissions: id-token"
|
|
305
|
+
request_url = os.environ.get("ACTIONS_ID_TOKEN_REQUEST_URL")
|
|
306
|
+
if not request_url:
|
|
307
|
+
raise ValueError(f"No ACTIONS_ID_TOKEN_REQUEST_URL - {tip}")
|
|
308
|
+
request_token = os.environ.get("ACTIONS_ID_TOKEN_REQUEST_TOKEN")
|
|
309
|
+
if not request_token:
|
|
310
|
+
raise ValueError(f"No ACTIONS_ID_TOKEN_REQUEST_TOKEN - {tip}")
|
|
311
|
+
if audience:
|
|
312
|
+
# request_url += f"&audience={quote(audience)}"
|
|
313
|
+
sep = "&" if "?" in request_url else "?"
|
|
314
|
+
request_url += f"{sep}audience={quote(audience)}"
|
|
315
|
+
|
|
316
|
+
request = Request(request_url)
|
|
317
|
+
request.add_header("Authorization", f"bearer {request_token}")
|
|
318
|
+
response = urlopen(request) # nosec
|
|
319
|
+
# code = response.getcode()
|
|
320
|
+
# if not 199 < code < 300:
|
|
321
|
+
# raise ValueError(f"Invalid response code: {code} - {tip}")
|
|
322
|
+
content = response.read().decode()
|
|
323
|
+
# print(f"content: {content}")
|
|
324
|
+
data = json.loads(content)
|
|
325
|
+
value = data.get("value")
|
|
326
|
+
if not value:
|
|
327
|
+
raise ValueError(f"No ID Token in response - {tip}")
|
|
328
|
+
mask(value)
|
|
329
|
+
return value
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
# PyGithub
|
|
333
|
+
# https://github.com/PyGithub/PyGithub
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def get_github(token: str, **kwargs) -> Github:
|
|
337
|
+
"""
|
|
338
|
+
Get Github from PyGithub
|
|
339
|
+
:param token: GitHub Token
|
|
340
|
+
:param kwargs: PyGithub kwargs
|
|
341
|
+
:return:
|
|
342
|
+
"""
|
|
343
|
+
if not _has_github: # pragma: no cover
|
|
344
|
+
raise ImportError("Install actions-tools[github] or PyGithub")
|
|
345
|
+
return Github(auth=Auth.Token(token), **kwargs)
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
# Additional
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
def get_event(path: Optional[str] = None) -> dict:
|
|
352
|
+
with open(path or os.environ["GITHUB_EVENT_PATH"]) as f:
|
|
353
|
+
return json.load(f)
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
def get_version(fallback: str = "Source") -> str:
|
|
357
|
+
workflow_ref: str = os.environ.get("GITHUB_WORKFLOW_REF", "")
|
|
358
|
+
if workflow_ref:
|
|
359
|
+
return workflow_ref.rsplit("/", 1)[-1]
|
|
360
|
+
return fallback
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
def get_random(length: int = 16) -> str:
|
|
364
|
+
r = "".join(secrets.choice(string.ascii_letters + string.digits) for _ in range(length))
|
|
365
|
+
return "".join(r)
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
def command(name: str, value: str = "", **kwargs):
|
|
369
|
+
cmd_args = _cmd_args(kwargs)
|
|
370
|
+
print(f"::{name}{cmd_args}::{value}", **kwargs)
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def start_indent(spaces: int = 2):
|
|
374
|
+
global _indent
|
|
375
|
+
_indent = spaces
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
def end_indent():
|
|
379
|
+
global _indent
|
|
380
|
+
_indent = 0
|
actions/py.typed
ADDED
|
File without changes
|