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 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