dycw-actions 0.8.11__py3-none-any.whl → 0.14.0__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/.DS_Store +0 -0
- actions/__init__.py +1 -1
- actions/clean_dir/lib.py +7 -4
- actions/cli.py +23 -2
- actions/constants.py +2 -0
- actions/git_clone_with/__init__.py +1 -0
- actions/git_clone_with/cli.py +41 -0
- actions/git_clone_with/constants.py +7 -0
- actions/git_clone_with/lib.py +78 -0
- actions/git_clone_with/settings.py +20 -0
- actions/pre_commit/conformalize_repo/action_dicts.py +39 -40
- actions/pre_commit/conformalize_repo/cli.py +16 -4
- actions/pre_commit/conformalize_repo/constants.py +14 -0
- actions/pre_commit/conformalize_repo/lib.py +350 -220
- actions/pre_commit/conformalize_repo/settings.py +42 -16
- actions/pre_commit/constants.py +3 -3
- actions/pre_commit/format_requirements/lib.py +5 -2
- actions/pre_commit/replace_sequence_strs/lib.py +5 -2
- actions/pre_commit/touch_empty_py/lib.py +7 -4
- actions/pre_commit/touch_py_typed/lib.py +9 -5
- actions/pre_commit/update_requirements/cli.py +10 -2
- actions/pre_commit/update_requirements/lib.py +78 -15
- actions/pre_commit/update_requirements/settings.py +23 -0
- actions/pre_commit/utilities.py +131 -39
- actions/publish_package/lib.py +33 -20
- actions/random_sleep/lib.py +12 -7
- actions/re_encrypt/__init__.py +1 -0
- actions/re_encrypt/cli.py +36 -0
- actions/re_encrypt/constants.py +7 -0
- actions/re_encrypt/lib.py +115 -0
- actions/re_encrypt/settings.py +26 -0
- actions/register_gitea_runner/configs/entrypoint.sh +8 -8
- actions/register_gitea_runner/lib.py +23 -14
- actions/run_hooks/lib.py +27 -14
- actions/setup_cronjob/lib.py +19 -13
- actions/setup_ssh_config/__init__.py +1 -0
- actions/setup_ssh_config/cli.py +17 -0
- actions/setup_ssh_config/constants.py +7 -0
- actions/setup_ssh_config/lib.py +30 -0
- actions/tag_commit/lib.py +16 -9
- actions/types.py +5 -9
- actions/utilities.py +1 -16
- {dycw_actions-0.8.11.dist-info → dycw_actions-0.14.0.dist-info}/METADATA +5 -3
- {dycw_actions-0.8.11.dist-info → dycw_actions-0.14.0.dist-info}/RECORD +46 -30
- {dycw_actions-0.8.11.dist-info → dycw_actions-0.14.0.dist-info}/WHEEL +1 -1
- {dycw_actions-0.8.11.dist-info → dycw_actions-0.14.0.dist-info}/entry_points.txt +0 -0
actions/pre_commit/utilities.py
CHANGED
|
@@ -5,7 +5,7 @@ from collections.abc import Iterator, MutableSet
|
|
|
5
5
|
from contextlib import contextmanager
|
|
6
6
|
from dataclasses import dataclass
|
|
7
7
|
from pathlib import Path
|
|
8
|
-
from typing import TYPE_CHECKING, Any, assert_never
|
|
8
|
+
from typing import TYPE_CHECKING, Any, assert_never, overload
|
|
9
9
|
|
|
10
10
|
import tomlkit
|
|
11
11
|
from libcst import Module, parse_module
|
|
@@ -16,8 +16,9 @@ from utilities.functions import ensure_class, ensure_str, get_func_name
|
|
|
16
16
|
from utilities.iterables import OneEmptyError, OneNonUniqueError, one
|
|
17
17
|
from utilities.packaging import Requirement
|
|
18
18
|
from utilities.types import PathLike, StrDict
|
|
19
|
+
from utilities.typing import is_str_dict
|
|
19
20
|
|
|
20
|
-
from actions.constants import PATH_CACHE, YAML_INSTANCE
|
|
21
|
+
from actions.constants import PATH_CACHE, PYPROJECT_TOML, YAML_INSTANCE
|
|
21
22
|
from actions.logging import LOGGER
|
|
22
23
|
from actions.utilities import are_equal_modulo_new_line, write_text, yaml_dump
|
|
23
24
|
|
|
@@ -26,26 +27,26 @@ if TYPE_CHECKING:
|
|
|
26
27
|
|
|
27
28
|
from utilities.types import PathLike, StrDict
|
|
28
29
|
|
|
29
|
-
from actions.types import
|
|
30
|
+
from actions.types import ArrayLike, ContainerLike, FuncRequirement
|
|
30
31
|
|
|
31
32
|
|
|
32
|
-
|
|
33
|
-
for table_ in tables:
|
|
34
|
-
if table_ not in array:
|
|
35
|
-
array.append(table_)
|
|
33
|
+
##
|
|
36
34
|
|
|
37
35
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
36
|
+
@overload
|
|
37
|
+
def ensure_contains(container: AoT, /, *objs: Table) -> None: ...
|
|
38
|
+
@overload
|
|
39
|
+
def ensure_contains(container: list[str], /, *objs: str) -> None: ...
|
|
40
|
+
@overload
|
|
41
|
+
def ensure_contains(container: list[StrDict], /, *objs: StrDict) -> None: ...
|
|
42
|
+
def ensure_contains(container: ArrayLike, /, *objs: Any) -> None:
|
|
42
43
|
for obj in objs:
|
|
43
|
-
if obj not in
|
|
44
|
-
|
|
44
|
+
if obj not in container:
|
|
45
|
+
container.append(obj)
|
|
45
46
|
|
|
46
47
|
|
|
47
48
|
def ensure_contains_partial_dict(
|
|
48
|
-
container:
|
|
49
|
+
container: list[StrDict], partial: StrDict, /, *, extra: StrDict | None = None
|
|
49
50
|
) -> StrDict:
|
|
50
51
|
try:
|
|
51
52
|
return get_partial_dict(container, partial, skip_log=True)
|
|
@@ -55,52 +56,123 @@ def ensure_contains_partial_dict(
|
|
|
55
56
|
return dict_
|
|
56
57
|
|
|
57
58
|
|
|
58
|
-
def ensure_contains_partial_str(
|
|
59
|
+
def ensure_contains_partial_str(list_: Array | list[str], text: str, /) -> str:
|
|
59
60
|
try:
|
|
60
|
-
return get_partial_str(
|
|
61
|
+
return get_partial_str(list_, text, skip_log=True)
|
|
61
62
|
except OneEmptyError:
|
|
62
|
-
|
|
63
|
+
list_.append(text)
|
|
63
64
|
return text
|
|
64
65
|
|
|
65
66
|
|
|
66
|
-
|
|
67
|
+
@overload
|
|
68
|
+
def ensure_not_contains(container: AoT, /, *objs: Table) -> None: ...
|
|
69
|
+
@overload
|
|
70
|
+
def ensure_not_contains(container: list[str], /, *objs: str) -> None: ...
|
|
71
|
+
@overload
|
|
72
|
+
def ensure_not_contains(container: list[StrDict], /, *objs: StrDict) -> None: ...
|
|
73
|
+
def ensure_not_contains(container: ArrayLike, /, *objs: Any) -> None:
|
|
67
74
|
for obj in objs:
|
|
68
75
|
try:
|
|
69
|
-
index = next(i for i, o in enumerate(
|
|
76
|
+
index = next(i for i, o in enumerate(container) if o == obj)
|
|
70
77
|
except StopIteration:
|
|
71
78
|
pass
|
|
72
79
|
else:
|
|
73
|
-
del
|
|
80
|
+
del container[index]
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
##
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def get_aot(container: ContainerLike, key: str, /) -> AoT:
|
|
87
|
+
return ensure_class(container[key], AoT)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def get_array(container: ContainerLike, key: str, /) -> Array:
|
|
91
|
+
return ensure_class(container[key], Array)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def get_dict(dict_: StrDict, key: str, /) -> StrDict:
|
|
95
|
+
if is_str_dict(value := dict_[key]):
|
|
96
|
+
return value
|
|
97
|
+
raise TypeError(value)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def get_list_dicts(dict_: StrDict, key: str, /) -> list[StrDict]:
|
|
101
|
+
list_ = ensure_class(dict_[key], list)
|
|
102
|
+
for i in list_:
|
|
103
|
+
if not is_str_dict(i):
|
|
104
|
+
raise TypeError(i)
|
|
105
|
+
return list_
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def get_list_strs(dict_: StrDict, key: str, /) -> list[str]:
|
|
109
|
+
list_ = ensure_class(dict_[key], list)
|
|
110
|
+
for i in list_:
|
|
111
|
+
if not isinstance(i, str):
|
|
112
|
+
raise TypeError(i)
|
|
113
|
+
return list_
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def get_table(container: ContainerLike, key: str, /) -> Table:
|
|
117
|
+
return ensure_class(container[key], Table)
|
|
74
118
|
|
|
75
119
|
|
|
76
120
|
##
|
|
77
121
|
|
|
78
122
|
|
|
79
|
-
def
|
|
80
|
-
|
|
123
|
+
def get_set_aot(container: ContainerLike, key: str, /) -> AoT:
|
|
124
|
+
try:
|
|
125
|
+
return get_aot(container, key)
|
|
126
|
+
except KeyError:
|
|
127
|
+
value = container[key] = aot()
|
|
128
|
+
return value
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def get_set_array(container: ContainerLike, key: str, /) -> Array:
|
|
132
|
+
try:
|
|
133
|
+
return get_array(container, key)
|
|
134
|
+
except KeyError:
|
|
135
|
+
value = container[key] = array()
|
|
136
|
+
return value
|
|
81
137
|
|
|
82
138
|
|
|
83
|
-
def
|
|
84
|
-
|
|
139
|
+
def get_set_dict(dict_: StrDict, key: str, /) -> StrDict:
|
|
140
|
+
try:
|
|
141
|
+
return get_dict(dict_, key)
|
|
142
|
+
except KeyError:
|
|
143
|
+
value = dict_[key] = {}
|
|
144
|
+
return value
|
|
85
145
|
|
|
86
146
|
|
|
87
|
-
def
|
|
88
|
-
|
|
147
|
+
def get_set_list_dicts(dict_: StrDict, key: str, /) -> list[StrDict]:
|
|
148
|
+
try:
|
|
149
|
+
return get_list_dicts(dict_, key)
|
|
150
|
+
except KeyError:
|
|
151
|
+
value = dict_[key] = []
|
|
152
|
+
return value
|
|
89
153
|
|
|
90
154
|
|
|
91
|
-
def
|
|
92
|
-
|
|
155
|
+
def get_set_list_strs(dict_: StrDict, key: str, /) -> list[str]:
|
|
156
|
+
try:
|
|
157
|
+
return get_list_strs(dict_, key)
|
|
158
|
+
except KeyError:
|
|
159
|
+
value = dict_[key] = []
|
|
160
|
+
return value
|
|
93
161
|
|
|
94
162
|
|
|
95
|
-
def
|
|
96
|
-
|
|
163
|
+
def get_set_table(container: ContainerLike, key: str, /) -> Table:
|
|
164
|
+
try:
|
|
165
|
+
return get_table(container, key)
|
|
166
|
+
except KeyError:
|
|
167
|
+
value = container[key] = table()
|
|
168
|
+
return value
|
|
97
169
|
|
|
98
170
|
|
|
99
171
|
##
|
|
100
172
|
|
|
101
173
|
|
|
102
174
|
def get_partial_dict(
|
|
103
|
-
iterable: Iterable[
|
|
175
|
+
iterable: Iterable[StrDict], dict_: StrDict, /, *, skip_log: bool = False
|
|
104
176
|
) -> StrDict:
|
|
105
177
|
try:
|
|
106
178
|
return one(i for i in iterable if is_partial_dict(dict_, i))
|
|
@@ -177,19 +249,21 @@ def is_partial_str(obj: Any, text: str, /) -> bool:
|
|
|
177
249
|
def get_pyproject_dependencies(doc: TOMLDocument, /) -> PyProjectDependencies:
|
|
178
250
|
out = PyProjectDependencies()
|
|
179
251
|
if (project_key := "project") in doc:
|
|
180
|
-
project =
|
|
252
|
+
project = get_set_table(doc, project_key)
|
|
181
253
|
if (dep_key := "dependencies") in project:
|
|
182
|
-
out.dependencies =
|
|
254
|
+
out.dependencies = get_set_array(project, dep_key)
|
|
183
255
|
if (opt_dep_key := "optional-dependencies") in project:
|
|
184
|
-
opt_dependencies =
|
|
256
|
+
opt_dependencies = get_set_table(project, opt_dep_key)
|
|
185
257
|
out.opt_dependencies = {}
|
|
186
258
|
for key in opt_dependencies:
|
|
187
|
-
out.opt_dependencies[ensure_str(key)] =
|
|
259
|
+
out.opt_dependencies[ensure_str(key)] = get_set_array(
|
|
260
|
+
opt_dependencies, key
|
|
261
|
+
)
|
|
188
262
|
if (dep_grps_key := "dependency-groups") in doc:
|
|
189
|
-
dep_grps =
|
|
263
|
+
dep_grps = get_set_table(doc, dep_grps_key)
|
|
190
264
|
out.dep_groups = {}
|
|
191
265
|
for key in dep_grps:
|
|
192
|
-
out.dep_groups[ensure_str(key)] =
|
|
266
|
+
out.dep_groups[ensure_str(key)] = get_set_array(dep_grps, key)
|
|
193
267
|
return out
|
|
194
268
|
|
|
195
269
|
|
|
@@ -295,6 +369,17 @@ def yield_json_dict(
|
|
|
295
369
|
##
|
|
296
370
|
|
|
297
371
|
|
|
372
|
+
@contextmanager
|
|
373
|
+
def yield_pyproject_toml(
|
|
374
|
+
*, modifications: MutableSet[Path] | None = None
|
|
375
|
+
) -> Iterator[TOMLDocument]:
|
|
376
|
+
with yield_toml_doc(PYPROJECT_TOML, modifications=modifications) as doc:
|
|
377
|
+
yield doc
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
##
|
|
381
|
+
|
|
382
|
+
|
|
298
383
|
@contextmanager
|
|
299
384
|
def yield_mutable_write_context[T](
|
|
300
385
|
path: PathLike,
|
|
@@ -370,7 +455,6 @@ def yield_yaml_dict(
|
|
|
370
455
|
__all__ = [
|
|
371
456
|
"PyProjectDependencies",
|
|
372
457
|
"WriteContext",
|
|
373
|
-
"ensure_aot_contains",
|
|
374
458
|
"ensure_contains",
|
|
375
459
|
"ensure_contains_partial_dict",
|
|
376
460
|
"ensure_contains_partial_str",
|
|
@@ -378,10 +462,17 @@ __all__ = [
|
|
|
378
462
|
"get_aot",
|
|
379
463
|
"get_array",
|
|
380
464
|
"get_dict",
|
|
381
|
-
"
|
|
465
|
+
"get_list_dicts",
|
|
466
|
+
"get_list_strs",
|
|
382
467
|
"get_partial_dict",
|
|
383
468
|
"get_partial_str",
|
|
384
469
|
"get_pyproject_dependencies",
|
|
470
|
+
"get_set_aot",
|
|
471
|
+
"get_set_array",
|
|
472
|
+
"get_set_dict",
|
|
473
|
+
"get_set_list_dicts",
|
|
474
|
+
"get_set_list_strs",
|
|
475
|
+
"get_set_table",
|
|
385
476
|
"get_table",
|
|
386
477
|
"is_partial_dict",
|
|
387
478
|
"is_partial_str",
|
|
@@ -389,6 +480,7 @@ __all__ = [
|
|
|
389
480
|
"yield_immutable_write_context",
|
|
390
481
|
"yield_json_dict",
|
|
391
482
|
"yield_mutable_write_context",
|
|
483
|
+
"yield_pyproject_toml",
|
|
392
484
|
"yield_python_file",
|
|
393
485
|
"yield_text_file",
|
|
394
486
|
"yield_toml_doc",
|
actions/publish_package/lib.py
CHANGED
|
@@ -2,15 +2,20 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from typing import TYPE_CHECKING
|
|
4
4
|
|
|
5
|
+
from utilities.functions import get_func_name
|
|
6
|
+
from utilities.tabulate import func_param_desc
|
|
5
7
|
from utilities.tempfile import TemporaryDirectory
|
|
6
8
|
|
|
9
|
+
from actions import __version__
|
|
7
10
|
from actions.logging import LOGGER
|
|
8
11
|
from actions.publish_package.settings import SETTINGS
|
|
9
|
-
from actions.utilities import
|
|
12
|
+
from actions.utilities import logged_run
|
|
10
13
|
|
|
11
14
|
if TYPE_CHECKING:
|
|
12
15
|
from typed_settings import Secret
|
|
13
16
|
|
|
17
|
+
from actions.types import SecretLike
|
|
18
|
+
|
|
14
19
|
|
|
15
20
|
def publish_package(
|
|
16
21
|
*,
|
|
@@ -20,26 +25,34 @@ def publish_package(
|
|
|
20
25
|
trusted_publishing: bool = SETTINGS.trusted_publishing,
|
|
21
26
|
native_tls: bool = SETTINGS.native_tls,
|
|
22
27
|
) -> None:
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
logged_run("uv", "build", "--out-dir", str(temp), "--wheel", "--clear")
|
|
33
|
-
logged_run(
|
|
34
|
-
"uv",
|
|
35
|
-
"publish",
|
|
36
|
-
*([] if username is None else ["--username", username]),
|
|
37
|
-
*([] if password is None else ["--password", password]),
|
|
38
|
-
*([] if publish_url is None else ["--publish-url", publish_url]),
|
|
39
|
-
*(["--trusted-publishing", "always"] if trusted_publishing else []),
|
|
40
|
-
*(["--native-tls"] if native_tls else []),
|
|
41
|
-
f"{temp}/*",
|
|
28
|
+
LOGGER.info(
|
|
29
|
+
func_param_desc(
|
|
30
|
+
publish_package,
|
|
31
|
+
__version__,
|
|
32
|
+
f"{username=}",
|
|
33
|
+
f"{password=}",
|
|
34
|
+
f"{publish_url=}",
|
|
35
|
+
f"{trusted_publishing=}",
|
|
36
|
+
f"{native_tls=}",
|
|
42
37
|
)
|
|
38
|
+
)
|
|
39
|
+
build_head: list[str] = ["uv", "build", "--out-dir"]
|
|
40
|
+
build_tail: list[str] = ["--wheel", "--clear"]
|
|
41
|
+
publish: list[SecretLike] = ["uv", "publish"]
|
|
42
|
+
if username is not None:
|
|
43
|
+
publish.extend(["--username", username])
|
|
44
|
+
if password is not None:
|
|
45
|
+
publish.extend(["--password", password])
|
|
46
|
+
if publish_url is not None:
|
|
47
|
+
publish.extend(["--publish-url", publish_url])
|
|
48
|
+
if trusted_publishing:
|
|
49
|
+
publish.extend(["--trusted-publishing", "always"])
|
|
50
|
+
if native_tls:
|
|
51
|
+
publish.append("--native-tls")
|
|
52
|
+
with TemporaryDirectory() as temp:
|
|
53
|
+
logged_run(*build_head, str(temp), *build_tail)
|
|
54
|
+
logged_run(*publish, f"{temp}/*")
|
|
55
|
+
LOGGER.info("Finished running %r", get_func_name(publish_package))
|
|
43
56
|
|
|
44
57
|
|
|
45
58
|
__all__ = ["publish_package"]
|
actions/random_sleep/lib.py
CHANGED
|
@@ -4,12 +4,14 @@ from math import ceil, floor
|
|
|
4
4
|
from random import choice
|
|
5
5
|
from time import sleep
|
|
6
6
|
|
|
7
|
+
from utilities.functions import get_func_name
|
|
8
|
+
from utilities.tabulate import func_param_desc
|
|
7
9
|
from utilities.whenever import get_now
|
|
8
10
|
from whenever import TimeDelta, ZonedDateTime
|
|
9
11
|
|
|
12
|
+
from actions import __version__
|
|
10
13
|
from actions.logging import LOGGER
|
|
11
14
|
from actions.random_sleep.settings import SETTINGS
|
|
12
|
-
from actions.utilities import log_func_call
|
|
13
15
|
|
|
14
16
|
|
|
15
17
|
def random_sleep(
|
|
@@ -19,15 +21,18 @@ def random_sleep(
|
|
|
19
21
|
step: int = SETTINGS.step,
|
|
20
22
|
log_freq: int = SETTINGS.log_freq,
|
|
21
23
|
) -> None:
|
|
22
|
-
|
|
23
|
-
|
|
24
|
+
LOGGER.info(
|
|
25
|
+
func_param_desc(
|
|
26
|
+
random_sleep, __version__, f"{min=}", f"{max=}", f"{step=}", f"{log_freq=}"
|
|
27
|
+
)
|
|
28
|
+
)
|
|
24
29
|
start = get_now()
|
|
25
|
-
|
|
26
|
-
LOGGER.info("Sleeping for %s...",
|
|
27
|
-
end = (start +
|
|
30
|
+
duration = TimeDelta(seconds=choice(range(min, max, step)))
|
|
31
|
+
LOGGER.info("Sleeping for %s...", duration)
|
|
32
|
+
end = (start + duration).round(mode="ceil")
|
|
28
33
|
while (now := get_now()) < end:
|
|
29
34
|
_intermediate(start, now, end, log_freq=log_freq)
|
|
30
|
-
LOGGER.info("Finished
|
|
35
|
+
LOGGER.info("Finished running %r", get_func_name(random_sleep))
|
|
31
36
|
|
|
32
37
|
|
|
33
38
|
def _intermediate(
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
import click
|
|
7
|
+
from click import argument
|
|
8
|
+
from typed_settings import click_options
|
|
9
|
+
from utilities.logging import basic_config
|
|
10
|
+
from utilities.os import is_pytest
|
|
11
|
+
|
|
12
|
+
from actions.logging import LOGGER
|
|
13
|
+
from actions.re_encrypt.lib import re_encrypt
|
|
14
|
+
from actions.re_encrypt.settings import Settings
|
|
15
|
+
from actions.utilities import LOADER
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from utilities.types import PathLike
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@argument("path", type=click.Path(exists=True, dir_okay=False, path_type=Path))
|
|
22
|
+
@click_options(Settings, [LOADER], show_envvars_in_help=True)
|
|
23
|
+
def re_encrypt_sub_cmd(settings: Settings, /, *, path: PathLike) -> None:
|
|
24
|
+
if is_pytest():
|
|
25
|
+
return
|
|
26
|
+
basic_config(obj=LOGGER)
|
|
27
|
+
re_encrypt(
|
|
28
|
+
path,
|
|
29
|
+
key_file=settings.key_file,
|
|
30
|
+
key=settings.key,
|
|
31
|
+
new_key_file=settings.new_key_file,
|
|
32
|
+
new_key=settings.new_key,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
__all__ = ["re_encrypt_sub_cmd"]
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from contextlib import contextmanager
|
|
4
|
+
from os import environ
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import TYPE_CHECKING, assert_never
|
|
7
|
+
|
|
8
|
+
from typed_settings import Secret
|
|
9
|
+
from utilities.atomicwrites import writer
|
|
10
|
+
from utilities.os import temp_environ
|
|
11
|
+
from utilities.subprocess import run
|
|
12
|
+
from utilities.tabulate import func_param_desc
|
|
13
|
+
from utilities.tempfile import TemporaryFile
|
|
14
|
+
from xdg_base_dirs import xdg_config_home
|
|
15
|
+
|
|
16
|
+
from actions import __version__
|
|
17
|
+
from actions.logging import LOGGER
|
|
18
|
+
from actions.re_encrypt.settings import SETTINGS
|
|
19
|
+
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
from collections.abc import Iterator
|
|
22
|
+
|
|
23
|
+
from utilities.types import PathLike
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def re_encrypt(
|
|
27
|
+
path: PathLike,
|
|
28
|
+
/,
|
|
29
|
+
*,
|
|
30
|
+
key_file: PathLike | None = SETTINGS.key_file,
|
|
31
|
+
key: Secret[str] | None = SETTINGS.key,
|
|
32
|
+
new_key_file: PathLike | None = SETTINGS.new_key_file,
|
|
33
|
+
new_key: Secret[str] | None = SETTINGS.new_key,
|
|
34
|
+
) -> None:
|
|
35
|
+
"""Re-encrypt a JSON file."""
|
|
36
|
+
LOGGER.info(
|
|
37
|
+
func_param_desc(
|
|
38
|
+
re_encrypt,
|
|
39
|
+
__version__,
|
|
40
|
+
f"{path=}",
|
|
41
|
+
f"{key_file=}",
|
|
42
|
+
f"{key=}",
|
|
43
|
+
f"{new_key_file=}",
|
|
44
|
+
f"{new_key=}",
|
|
45
|
+
)
|
|
46
|
+
)
|
|
47
|
+
with _yield_env(key_file=key_file, key=key):
|
|
48
|
+
decrypted = run(
|
|
49
|
+
"sops",
|
|
50
|
+
"decrypt",
|
|
51
|
+
"--input-type",
|
|
52
|
+
"json",
|
|
53
|
+
"--output-type",
|
|
54
|
+
"json",
|
|
55
|
+
"--ignore-mac",
|
|
56
|
+
str(path),
|
|
57
|
+
return_=True,
|
|
58
|
+
)
|
|
59
|
+
with _yield_env(key_file=new_key_file, key=new_key):
|
|
60
|
+
identity = _get_recipient()
|
|
61
|
+
with TemporaryFile(text=decrypted) as temp:
|
|
62
|
+
encrypted = run(
|
|
63
|
+
"sops",
|
|
64
|
+
"encrypt",
|
|
65
|
+
"--age",
|
|
66
|
+
identity,
|
|
67
|
+
"--input-type",
|
|
68
|
+
"json",
|
|
69
|
+
"--output-type",
|
|
70
|
+
"json",
|
|
71
|
+
str(temp),
|
|
72
|
+
return_=True,
|
|
73
|
+
)
|
|
74
|
+
with writer(path, overwrite=True) as temp:
|
|
75
|
+
_ = temp.write_text(encrypted)
|
|
76
|
+
LOGGER.info("Finished re-encrypting '%s'", path)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@contextmanager
|
|
80
|
+
def _yield_env(
|
|
81
|
+
*,
|
|
82
|
+
key_file: PathLike | None = SETTINGS.key_file,
|
|
83
|
+
key: Secret[str] | None = SETTINGS.key,
|
|
84
|
+
) -> Iterator[None]:
|
|
85
|
+
match key_file, key:
|
|
86
|
+
case Path() | str(), _:
|
|
87
|
+
with temp_environ(SOPS_AGE_KEY_FILE=str(key_file)):
|
|
88
|
+
yield
|
|
89
|
+
case None, Secret():
|
|
90
|
+
with temp_environ(SOPS_AGE_KEY=key.get_secret_value()):
|
|
91
|
+
yield
|
|
92
|
+
case None, None:
|
|
93
|
+
path = xdg_config_home() / "sops/age/keys.txt"
|
|
94
|
+
with temp_environ(SOPS_AGE_KEY_FILE=str(path)):
|
|
95
|
+
yield
|
|
96
|
+
case never:
|
|
97
|
+
assert_never(never)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _get_recipient() -> str:
|
|
101
|
+
try:
|
|
102
|
+
key_file = environ["SOPS_AGE_KEY_FILE"]
|
|
103
|
+
except KeyError:
|
|
104
|
+
with TemporaryFile(text=environ["SOPS_AGE_KEY"]) as temp:
|
|
105
|
+
return _get_recipient_from_path(temp)
|
|
106
|
+
else:
|
|
107
|
+
return _get_recipient_from_path(key_file)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _get_recipient_from_path(path: PathLike, /) -> str:
|
|
111
|
+
recipient, *_ = run("age-keygen", "-y", str(path), return_=True).splitlines()
|
|
112
|
+
return recipient
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
__all__ = ["re_encrypt"]
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# ruff: noqa: TC003
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from typed_settings import Secret, load_settings, option, secret, settings
|
|
7
|
+
|
|
8
|
+
from actions.utilities import LOADER
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@settings
|
|
12
|
+
class Settings:
|
|
13
|
+
key_file: Path | None = option(default=None, help="The key file")
|
|
14
|
+
key: Secret[str] | None = secret(default=None, help="The age identity")
|
|
15
|
+
new_key_file: Path | None = option(
|
|
16
|
+
default=None, help="The new key file for encryption"
|
|
17
|
+
)
|
|
18
|
+
new_key: Secret[str] | None = secret(
|
|
19
|
+
default=None, help="The new age identity for encryption"
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
SETTINGS = load_settings(Settings, [LOADER])
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
__all__ = ["SETTINGS", "Settings"]
|
|
@@ -6,16 +6,16 @@ echo_date() { echo "[$(date +'%Y-%m-%d %H:%M:%S')] $*" >&2; }
|
|
|
6
6
|
|
|
7
7
|
# main
|
|
8
8
|
wait-for-it.sh \
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
--host="${GITEA_HOST}" \
|
|
10
|
+
--port="${GITEA_PORT}" \
|
|
11
|
+
--strict \
|
|
12
|
+
-- \
|
|
13
|
+
echo "${GITEA_HOST}:${GITEA_PORT} is up"
|
|
14
14
|
|
|
15
15
|
if ! command -v update-ca-certificates >/dev/null 2>&1; then
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
echo_date "Installing 'ca-certificates'..."
|
|
17
|
+
apk update
|
|
18
|
+
apk add --no-cache ca-certificates
|
|
19
19
|
fi
|
|
20
20
|
|
|
21
21
|
update-ca-certificates || true
|
|
@@ -7,8 +7,11 @@ from typing import TYPE_CHECKING
|
|
|
7
7
|
|
|
8
8
|
from requests import get
|
|
9
9
|
from utilities.atomicwrites import writer
|
|
10
|
+
from utilities.functions import get_func_name
|
|
10
11
|
from utilities.subprocess import chmod, rm_cmd, ssh, sudo_cmd
|
|
12
|
+
from utilities.tabulate import func_param_desc
|
|
11
13
|
|
|
14
|
+
from actions import __version__
|
|
12
15
|
from actions.logging import LOGGER
|
|
13
16
|
from actions.register_gitea_runner.constants import (
|
|
14
17
|
PATH_CACHE,
|
|
@@ -17,7 +20,7 @@ from actions.register_gitea_runner.constants import (
|
|
|
17
20
|
URL_WAIT_FOR_IT,
|
|
18
21
|
)
|
|
19
22
|
from actions.register_gitea_runner.settings import SETTINGS
|
|
20
|
-
from actions.utilities import
|
|
23
|
+
from actions.utilities import logged_run
|
|
21
24
|
|
|
22
25
|
if TYPE_CHECKING:
|
|
23
26
|
from utilities.types import PathLike
|
|
@@ -37,19 +40,22 @@ def register_gitea_runner(
|
|
|
37
40
|
runner_instance_name: str = SETTINGS.runner_instance_name,
|
|
38
41
|
) -> None:
|
|
39
42
|
"""Register against a remote instance of Gitea."""
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
43
|
+
LOGGER.info(
|
|
44
|
+
func_param_desc(
|
|
45
|
+
register_gitea_runner,
|
|
46
|
+
__version__,
|
|
47
|
+
f"{ssh_user=}",
|
|
48
|
+
f"{ssh_host=}",
|
|
49
|
+
f"{gitea_container_user=}",
|
|
50
|
+
f"{gitea_container_name=}",
|
|
51
|
+
f"{runner_certificate=}",
|
|
52
|
+
f"{runner_capacity=}",
|
|
53
|
+
f"{runner_container_name=}",
|
|
54
|
+
f"{gitea_host=}",
|
|
55
|
+
f"{gitea_port=}",
|
|
56
|
+
f"{runner_instance_name=}",
|
|
57
|
+
)
|
|
58
|
+
)
|
|
53
59
|
token = ssh(
|
|
54
60
|
ssh_user,
|
|
55
61
|
ssh_host,
|
|
@@ -66,6 +72,7 @@ def register_gitea_runner(
|
|
|
66
72
|
gitea_port=gitea_port,
|
|
67
73
|
runner_instance_name=runner_instance_name,
|
|
68
74
|
)
|
|
75
|
+
LOGGER.info("Finished running %r", get_func_name(register_gitea_runner))
|
|
69
76
|
|
|
70
77
|
|
|
71
78
|
def register_against_local(
|
|
@@ -155,6 +162,8 @@ def _docker_run_act_runner_args(
|
|
|
155
162
|
f"GITEA_RUNNER_REGISTRATION_TOKEN={token}",
|
|
156
163
|
"--name",
|
|
157
164
|
container_name,
|
|
165
|
+
"--restart",
|
|
166
|
+
"always",
|
|
158
167
|
"--volume",
|
|
159
168
|
"/var/run/docker.sock:/var/run/docker.sock",
|
|
160
169
|
"--volume",
|