saltext.github 1.1.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.
- saltext/github/__init__.py +31 -0
- saltext/github/modules/__init__.py +0 -0
- saltext/github/modules/github.py +1873 -0
- saltext/github/modules/github_org.py +667 -0
- saltext/github/modules/github_repo.py +1658 -0
- saltext/github/modules/github_user.py +75 -0
- saltext/github/resources/__init__.py +0 -0
- saltext/github/resources/github/__init__.py +251 -0
- saltext/github/resources/github/modules/__init__.py +0 -0
- saltext/github/resources/github/modules/actions.py +175 -0
- saltext/github/resources/github/modules/config.py +69 -0
- saltext/github/resources/github/modules/deploy_key.py +122 -0
- saltext/github/resources/github/modules/environment.py +241 -0
- saltext/github/resources/github/modules/grains.py +18 -0
- saltext/github/resources/github/modules/issue.py +157 -0
- saltext/github/resources/github/modules/label.py +161 -0
- saltext/github/resources/github/modules/milestone.py +103 -0
- saltext/github/resources/github/modules/pages.py +149 -0
- saltext/github/resources/github/modules/pull.py +90 -0
- saltext/github/resources/github/modules/repo.py +155 -0
- saltext/github/resources/github/modules/ruleset.py +144 -0
- saltext/github/resources/github/modules/saltutil.py +24 -0
- saltext/github/resources/github/modules/state.py +116 -0
- saltext/github/resources/github/modules/test.py +29 -0
- saltext/github/resources/github/states/__init__.py +0 -0
- saltext/github/resources/github/states/actions.py +117 -0
- saltext/github/resources/github/states/deploy_key.py +130 -0
- saltext/github/resources/github/states/environment.py +99 -0
- saltext/github/resources/github/states/label.py +71 -0
- saltext/github/resources/github/states/pages.py +92 -0
- saltext/github/resources/github/states/repo.py +108 -0
- saltext/github/resources/github/states/ruleset.py +81 -0
- saltext/github/states/__init__.py +0 -0
- saltext/github/states/github.py +966 -0
- saltext/github/states/github_org.py +369 -0
- saltext/github/states/github_repo.py +722 -0
- saltext/github/utils/__init__.py +72 -0
- saltext/github/utils/gh/__init__.py +14 -0
- saltext/github/utils/gh/actions.py +195 -0
- saltext/github/utils/gh/cache.py +126 -0
- saltext/github/utils/gh/client.py +534 -0
- saltext/github/utils/gh/deploy_keys.py +121 -0
- saltext/github/utils/gh/environments.py +271 -0
- saltext/github/utils/gh/exceptions.py +28 -0
- saltext/github/utils/gh/grains_files.py +366 -0
- saltext/github/utils/gh/issues.py +193 -0
- saltext/github/utils/gh/labels.py +142 -0
- saltext/github/utils/gh/members.py +157 -0
- saltext/github/utils/gh/milestones.py +122 -0
- saltext/github/utils/gh/pages.py +128 -0
- saltext/github/utils/gh/profile.py +458 -0
- saltext/github/utils/gh/pulls.py +99 -0
- saltext/github/utils/gh/repos.py +465 -0
- saltext/github/utils/gh/resource.py +35 -0
- saltext/github/utils/gh/rulesets.py +191 -0
- saltext/github/utils/gh/statecores/__init__.py +70 -0
- saltext/github/utils/gh/statecores/actions.py +180 -0
- saltext/github/utils/gh/statecores/deploy_keys.py +241 -0
- saltext/github/utils/gh/statecores/environments.py +212 -0
- saltext/github/utils/gh/statecores/labels.py +116 -0
- saltext/github/utils/gh/statecores/pages.py +177 -0
- saltext/github/utils/gh/statecores/repo.py +249 -0
- saltext/github/utils/gh/statecores/rulesets.py +115 -0
- saltext/github/utils/gh/teams.py +375 -0
- saltext/github/utils/gh/users.py +87 -0
- saltext/github/version.py +1 -0
- saltext_github-1.1.0.dist-info/METADATA +151 -0
- saltext_github-1.1.0.dist-info/RECORD +73 -0
- saltext_github-1.1.0.dist-info/WHEEL +5 -0
- saltext_github-1.1.0.dist-info/entry_points.txt +2 -0
- saltext_github-1.1.0.dist-info/licenses/LICENSE +201 -0
- saltext_github-1.1.0.dist-info/licenses/NOTICE +10 -0
- saltext_github-1.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import typing
|
|
2
|
+
from collections.abc import Mapping
|
|
3
|
+
from types import EllipsisType
|
|
4
|
+
|
|
5
|
+
from salt.exceptions import CommandExecutionError
|
|
6
|
+
|
|
7
|
+
K = typing.TypeVar("K")
|
|
8
|
+
V = typing.TypeVar("V")
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@typing.overload
|
|
12
|
+
def filter_unset(
|
|
13
|
+
data: Mapping[K, V | None],
|
|
14
|
+
unset: None = None,
|
|
15
|
+
) -> dict[K, V]: ...
|
|
16
|
+
@typing.overload
|
|
17
|
+
def filter_unset(
|
|
18
|
+
data: Mapping[K, V | EllipsisType],
|
|
19
|
+
unset: EllipsisType,
|
|
20
|
+
) -> dict[K, V]: ...
|
|
21
|
+
@typing.overload
|
|
22
|
+
def filter_unset(
|
|
23
|
+
data: Mapping[K, V],
|
|
24
|
+
unset: object,
|
|
25
|
+
) -> dict[K, V]: ...
|
|
26
|
+
def filter_unset(
|
|
27
|
+
data: Mapping[K, V],
|
|
28
|
+
unset: object = None,
|
|
29
|
+
) -> dict[K, object]:
|
|
30
|
+
"""
|
|
31
|
+
Remove all unset values in a dictionary and return the filtered result.
|
|
32
|
+
|
|
33
|
+
data
|
|
34
|
+
Dictionary to filter.
|
|
35
|
+
|
|
36
|
+
unset
|
|
37
|
+
Identity of what counts as unset; evaluated via ``is not``.
|
|
38
|
+
Defaults to ``None``. Other working examples: ``...`` (ellipsis),
|
|
39
|
+
a specific object such as ``salt.defaults.NOT_SET`` or
|
|
40
|
+
a string literal (not recommended though).
|
|
41
|
+
"""
|
|
42
|
+
return {k: v for k, v in data.items() if v is not unset}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def filter_fields(data, fields):
|
|
46
|
+
"""
|
|
47
|
+
Reduce the entities in a collection to a subset of their fields.
|
|
48
|
+
|
|
49
|
+
data
|
|
50
|
+
List of mappings or mapping of key to mapping.
|
|
51
|
+
|
|
52
|
+
fields
|
|
53
|
+
Single field name or list of field names. A list reduces each
|
|
54
|
+
entity to a mapping of the listed fields, a single name to the
|
|
55
|
+
field's value. ``None`` (the default) returns ``data`` unchanged.
|
|
56
|
+
"""
|
|
57
|
+
if fields is None:
|
|
58
|
+
return data
|
|
59
|
+
|
|
60
|
+
def _reduce(entity):
|
|
61
|
+
try:
|
|
62
|
+
if isinstance(fields, str):
|
|
63
|
+
return entity[fields]
|
|
64
|
+
return {field: entity[field] for field in fields}
|
|
65
|
+
except KeyError as err:
|
|
66
|
+
raise CommandExecutionError(
|
|
67
|
+
f"Unknown field {err}. Available fields: {', '.join(entity)}"
|
|
68
|
+
) from None
|
|
69
|
+
|
|
70
|
+
if isinstance(data, Mapping):
|
|
71
|
+
return {key: _reduce(val) for key, val in data.items()}
|
|
72
|
+
return [_reduce(entity) for entity in data]
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Connection library for GitHub.
|
|
3
|
+
|
|
4
|
+
The submodules of this package provide reusable primitives for talking
|
|
5
|
+
to the GitHub API on behalf of an organization, built around
|
|
6
|
+
:class:`~saltext.github.utils.gh.client.GitHubConnection`.
|
|
7
|
+
They raise :class:`~saltext.github.utils.gh.exceptions.GitHubApiError`
|
|
8
|
+
(or its more specific subclass
|
|
9
|
+
:class:`~saltext.github.utils.gh.exceptions.GitHubNotFoundError`)
|
|
10
|
+
on API errors.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from saltext.github.utils.gh.exceptions import GitHubApiError # noqa: F401
|
|
14
|
+
from saltext.github.utils.gh.exceptions import GitHubNotFoundError # noqa: F401
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Operations on GitHub Actions repository secrets and settings.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import base64
|
|
6
|
+
import logging
|
|
7
|
+
|
|
8
|
+
from nacl import encoding
|
|
9
|
+
from nacl import public
|
|
10
|
+
|
|
11
|
+
from saltext.github.utils import filter_unset
|
|
12
|
+
|
|
13
|
+
log = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def list_secrets(conn, repo_name, *, owner=None):
|
|
17
|
+
"""
|
|
18
|
+
Return all Actions secrets of a repository. Secret values are write-only,
|
|
19
|
+
so this only carries metadata (``name``, ``created_at``, ``updated_at``).
|
|
20
|
+
|
|
21
|
+
conn
|
|
22
|
+
:class:`~saltext.github.utils.gh.client.GitHubConnection` to use.
|
|
23
|
+
|
|
24
|
+
repo_name
|
|
25
|
+
Repository name.
|
|
26
|
+
|
|
27
|
+
owner
|
|
28
|
+
Owner of the repository. Defaults to the connection's owner.
|
|
29
|
+
"""
|
|
30
|
+
return conn.repo_query(owner, repo_name, "actions", "secrets", per_page=100)["secrets"]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def get_secret(conn, repo_name, name, *, owner=None):
|
|
34
|
+
"""
|
|
35
|
+
Return the metadata of a single Actions secret.
|
|
36
|
+
|
|
37
|
+
conn
|
|
38
|
+
:class:`~saltext.github.utils.gh.client.GitHubConnection` to use.
|
|
39
|
+
|
|
40
|
+
repo_name
|
|
41
|
+
Repository name.
|
|
42
|
+
|
|
43
|
+
name
|
|
44
|
+
Secret name.
|
|
45
|
+
|
|
46
|
+
owner
|
|
47
|
+
Owner of the repository. Defaults to the connection's owner.
|
|
48
|
+
|
|
49
|
+
Raises :class:`~saltext.github.utils.gh.exceptions.GitHubNotFoundError`
|
|
50
|
+
if the secret does not exist.
|
|
51
|
+
"""
|
|
52
|
+
return conn.repo_query(owner, repo_name, "actions", "secrets", name)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def get_public_key(conn, repo_name, *, owner=None):
|
|
56
|
+
"""
|
|
57
|
+
Return the public key used for encrypting Actions secrets of a repository,
|
|
58
|
+
a mapping with ``key_id`` and (base64-encoded) ``key``.
|
|
59
|
+
|
|
60
|
+
conn
|
|
61
|
+
:class:`~saltext.github.utils.gh.client.GitHubConnection` to use.
|
|
62
|
+
|
|
63
|
+
repo_name
|
|
64
|
+
Repository name.
|
|
65
|
+
|
|
66
|
+
owner
|
|
67
|
+
Owner of the repository. Defaults to the connection's owner.
|
|
68
|
+
"""
|
|
69
|
+
return conn.repo_query(owner, repo_name, "actions", "secrets", "public-key")
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def encrypt_secret(public_key, value):
|
|
73
|
+
"""
|
|
74
|
+
Encrypt a secret value for upload using a repository public key
|
|
75
|
+
(libsodium sealed box). Returns the base64-encoded ciphertext.
|
|
76
|
+
|
|
77
|
+
public_key
|
|
78
|
+
Base64-encoded public key of the repository, as returned
|
|
79
|
+
by :func:`get_public_key` in ``key``.
|
|
80
|
+
|
|
81
|
+
value
|
|
82
|
+
Secret value to encrypt.
|
|
83
|
+
"""
|
|
84
|
+
sealed_box = public.SealedBox(public.PublicKey(public_key.encode(), encoding.Base64Encoder))
|
|
85
|
+
return base64.b64encode(sealed_box.encrypt(value.encode())).decode()
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def set_secret(conn, repo_name, name, value, *, owner=None):
|
|
89
|
+
"""
|
|
90
|
+
Create or update an Actions secret. The value is encrypted to the
|
|
91
|
+
repository public key before leaving this host.
|
|
92
|
+
|
|
93
|
+
conn
|
|
94
|
+
:class:`~saltext.github.utils.gh.client.GitHubConnection` to use.
|
|
95
|
+
|
|
96
|
+
repo_name
|
|
97
|
+
Repository name.
|
|
98
|
+
|
|
99
|
+
name
|
|
100
|
+
Secret name.
|
|
101
|
+
|
|
102
|
+
value
|
|
103
|
+
Plaintext secret value.
|
|
104
|
+
|
|
105
|
+
owner
|
|
106
|
+
Owner of the repository. Defaults to the connection's owner.
|
|
107
|
+
"""
|
|
108
|
+
pubkey = get_public_key(conn, repo_name, owner=owner)
|
|
109
|
+
conn.repo_query(
|
|
110
|
+
owner,
|
|
111
|
+
*(repo_name, "actions", "secrets", name),
|
|
112
|
+
method="PUT",
|
|
113
|
+
data={
|
|
114
|
+
"encrypted_value": encrypt_secret(pubkey["key"], value),
|
|
115
|
+
"key_id": pubkey["key_id"],
|
|
116
|
+
},
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def delete_secret(conn, repo_name, name, *, owner=None):
|
|
121
|
+
"""
|
|
122
|
+
Remove an Actions secret from a repository.
|
|
123
|
+
|
|
124
|
+
conn
|
|
125
|
+
:class:`~saltext.github.utils.gh.client.GitHubConnection` to use.
|
|
126
|
+
|
|
127
|
+
repo_name
|
|
128
|
+
Repository name.
|
|
129
|
+
|
|
130
|
+
name
|
|
131
|
+
Secret name.
|
|
132
|
+
|
|
133
|
+
owner
|
|
134
|
+
Owner of the repository. Defaults to the connection's owner.
|
|
135
|
+
"""
|
|
136
|
+
conn.repo_query(owner, repo_name, "actions", "secrets", name, method="DELETE")
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def get_workflow_permissions(conn, repo_name, *, owner=None):
|
|
140
|
+
"""
|
|
141
|
+
Return the default workflow permissions granted to the ``GITHUB_TOKEN``
|
|
142
|
+
in a repository, a mapping with ``default_workflow_permissions``
|
|
143
|
+
(``read``/``write``) and ``can_approve_pull_request_reviews``.
|
|
144
|
+
|
|
145
|
+
conn
|
|
146
|
+
:class:`~saltext.github.utils.gh.client.GitHubConnection` to use.
|
|
147
|
+
|
|
148
|
+
repo_name
|
|
149
|
+
Repository name.
|
|
150
|
+
|
|
151
|
+
owner
|
|
152
|
+
Owner of the repository. Defaults to the connection's owner.
|
|
153
|
+
"""
|
|
154
|
+
return conn.repo_query(owner, repo_name, "actions", "permissions", "workflow")
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def set_workflow_permissions(
|
|
158
|
+
conn,
|
|
159
|
+
repo_name,
|
|
160
|
+
*,
|
|
161
|
+
default_workflow_permissions=None,
|
|
162
|
+
can_approve_pull_request_reviews=None,
|
|
163
|
+
owner=None,
|
|
164
|
+
):
|
|
165
|
+
"""
|
|
166
|
+
Update the default workflow permissions granted to the ``GITHUB_TOKEN``
|
|
167
|
+
in a repository.
|
|
168
|
+
|
|
169
|
+
conn
|
|
170
|
+
:class:`~saltext.github.utils.gh.client.GitHubConnection` to use.
|
|
171
|
+
|
|
172
|
+
repo_name
|
|
173
|
+
Repository name.
|
|
174
|
+
|
|
175
|
+
default_workflow_permissions
|
|
176
|
+
Default permissions for the ``GITHUB_TOKEN``, either ``read``
|
|
177
|
+
or ``write``.
|
|
178
|
+
|
|
179
|
+
can_approve_pull_request_reviews
|
|
180
|
+
Allow GitHub Actions to approve pull requests.
|
|
181
|
+
|
|
182
|
+
owner
|
|
183
|
+
Owner of the repository. Defaults to the connection's owner.
|
|
184
|
+
"""
|
|
185
|
+
conn.repo_query(
|
|
186
|
+
owner,
|
|
187
|
+
*(repo_name, "actions", "permissions", "workflow"),
|
|
188
|
+
method="PUT",
|
|
189
|
+
data=filter_unset(
|
|
190
|
+
{
|
|
191
|
+
"default_workflow_permissions": default_workflow_permissions,
|
|
192
|
+
"can_approve_pull_request_reviews": can_approve_pull_request_reviews,
|
|
193
|
+
}
|
|
194
|
+
),
|
|
195
|
+
)
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Optional persistent cross-process cache for slow-changing GitHub API
|
|
3
|
+
data, backed by the minion's :py:mod:`salt.cache` (``cachedir``).
|
|
4
|
+
|
|
5
|
+
Attached to every profile-resolved
|
|
6
|
+
:class:`~saltext.github.utils.gh.client.GitHubConnection` as its
|
|
7
|
+
``store`` unless the profile opts out via :ghconf:`cache`. Particularly
|
|
8
|
+
useful for ``salt-call --local`` setups, where every invocation starts
|
|
9
|
+
a fresh process and the in-memory caches never survive.
|
|
10
|
+
|
|
11
|
+
Two kinds of data are stored, namespaced by a token/API URL-derived
|
|
12
|
+
identity so rotated credentials or different endpoints never share
|
|
13
|
+
entries:
|
|
14
|
+
|
|
15
|
+
* Repository file contents (fetched for
|
|
16
|
+
:ghconf:`resource:grains_files`), validated against the repository's
|
|
17
|
+
``pushed_at`` timestamp – entries are served until the repository
|
|
18
|
+
receives a new push. Negative lookups (missing files) are cached the
|
|
19
|
+
same way.
|
|
20
|
+
* Metadata that is stable for a given token/owner: account types
|
|
21
|
+
(organization vs user) and the token's user login. These are stored
|
|
22
|
+
indefinitely.
|
|
23
|
+
|
|
24
|
+
All storage errors degrade gracefully to cache misses.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
import logging
|
|
28
|
+
|
|
29
|
+
log = logging.getLogger(__name__)
|
|
30
|
+
|
|
31
|
+
CACHE_ROOT = "saltext_github"
|
|
32
|
+
#: Sentinel for missing cache entries (``None`` is a valid cached value).
|
|
33
|
+
MISS = object()
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class PersistentCache:
|
|
37
|
+
"""
|
|
38
|
+
``salt.cache``-backed store, see the module docstring.
|
|
39
|
+
|
|
40
|
+
opts
|
|
41
|
+
The minion configuration (``__opts__``), selecting the cache
|
|
42
|
+
backend and ``cachedir``.
|
|
43
|
+
|
|
44
|
+
ident
|
|
45
|
+
Identity string namespacing all entries, derived from the
|
|
46
|
+
connection's token and API URL
|
|
47
|
+
(:attr:`~saltext.github.utils.gh.client.GitHubConnection.ident`).
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
def __init__(self, opts, ident):
|
|
51
|
+
# Local import: salt.cache pulls in the loader machinery, which
|
|
52
|
+
# not all importers of this package need.
|
|
53
|
+
import salt.cache # pylint: disable=import-outside-toplevel
|
|
54
|
+
|
|
55
|
+
self._cache = salt.cache.factory(opts)
|
|
56
|
+
self._base = f"{CACHE_ROOT}/{ident}"
|
|
57
|
+
|
|
58
|
+
def _fetch(self, bank, key):
|
|
59
|
+
try:
|
|
60
|
+
return self._cache.fetch(f"{self._base}/{bank}", key)
|
|
61
|
+
except Exception as err: # pylint: disable=broad-except
|
|
62
|
+
log.warning("Failed reading from the persistent GitHub cache: %s", err)
|
|
63
|
+
return None
|
|
64
|
+
|
|
65
|
+
def _store(self, bank, key, value):
|
|
66
|
+
try:
|
|
67
|
+
self._cache.store(f"{self._base}/{bank}", key, value)
|
|
68
|
+
except Exception as err: # pylint: disable=broad-except
|
|
69
|
+
log.warning("Failed writing to the persistent GitHub cache: %s", err)
|
|
70
|
+
|
|
71
|
+
def get_meta(self, key):
|
|
72
|
+
"""
|
|
73
|
+
Return a stored metadata value, or :data:`MISS`.
|
|
74
|
+
"""
|
|
75
|
+
data = self._fetch("meta", key)
|
|
76
|
+
if isinstance(data, dict) and "value" in data:
|
|
77
|
+
return data["value"]
|
|
78
|
+
return MISS
|
|
79
|
+
|
|
80
|
+
def set_meta(self, key, value):
|
|
81
|
+
"""
|
|
82
|
+
Store a metadata value.
|
|
83
|
+
"""
|
|
84
|
+
self._store("meta", key, {"value": value})
|
|
85
|
+
|
|
86
|
+
def get_file(self, owner, repo, path, pushed_at):
|
|
87
|
+
"""
|
|
88
|
+
Return a repository file's stored content (which is ``None`` for
|
|
89
|
+
cached negative lookups), or :data:`MISS` when there is no entry
|
|
90
|
+
or the stored entry does not match ``pushed_at``.
|
|
91
|
+
"""
|
|
92
|
+
if not pushed_at:
|
|
93
|
+
return MISS
|
|
94
|
+
entry = self._fetch(f"files/{owner.lower()}", repo.lower())
|
|
95
|
+
if (
|
|
96
|
+
isinstance(entry, dict)
|
|
97
|
+
and entry.get("pushed_at") == pushed_at
|
|
98
|
+
and path in entry.get("files", {})
|
|
99
|
+
):
|
|
100
|
+
return entry["files"][path]
|
|
101
|
+
return MISS
|
|
102
|
+
|
|
103
|
+
def set_file(self, owner, repo, path, content, pushed_at):
|
|
104
|
+
"""
|
|
105
|
+
Store a repository file's content (``None`` for a negative
|
|
106
|
+
lookup) under the repository's current ``pushed_at``. Entries
|
|
107
|
+
stored for a different ``pushed_at`` are discarded.
|
|
108
|
+
"""
|
|
109
|
+
if not pushed_at:
|
|
110
|
+
return
|
|
111
|
+
bank = f"files/{owner.lower()}"
|
|
112
|
+
key = repo.lower()
|
|
113
|
+
entry = self._fetch(bank, key)
|
|
114
|
+
if not isinstance(entry, dict) or entry.get("pushed_at") != pushed_at:
|
|
115
|
+
entry = {"pushed_at": pushed_at, "files": {}}
|
|
116
|
+
entry["files"][path] = content
|
|
117
|
+
self._store(bank, key, entry)
|
|
118
|
+
|
|
119
|
+
def flush_repo(self, owner, repo):
|
|
120
|
+
"""
|
|
121
|
+
Drop all stored file contents of one repository.
|
|
122
|
+
"""
|
|
123
|
+
try:
|
|
124
|
+
self._cache.flush(f"{self._base}/files/{owner.lower()}", repo.lower())
|
|
125
|
+
except Exception as err: # pylint: disable=broad-except
|
|
126
|
+
log.warning("Failed flushing the persistent GitHub cache: %s", err)
|