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,465 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Operations on GitHub repositories. Repository listing and creation are
|
|
3
|
+
organization-scoped, everything else works on any owner's repositories.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import base64
|
|
7
|
+
import logging
|
|
8
|
+
import time
|
|
9
|
+
|
|
10
|
+
from github.GithubException import UnknownObjectException
|
|
11
|
+
from salt.exceptions import CommandExecutionError
|
|
12
|
+
|
|
13
|
+
from saltext.github.utils import filter_unset
|
|
14
|
+
from saltext.github.utils.gh.cache import MISS
|
|
15
|
+
from saltext.github.utils.gh.client import api_request
|
|
16
|
+
from saltext.github.utils.gh.exceptions import GitHubNotFoundError
|
|
17
|
+
|
|
18
|
+
log = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def to_dict(repo):
|
|
22
|
+
"""
|
|
23
|
+
Render a PyGithub ``Repository`` object into a plain dictionary,
|
|
24
|
+
excluding GitHub's deprecated alias fields (``watchers_count``,
|
|
25
|
+
``watchers``, ``forks``, ``open_issues``).
|
|
26
|
+
"""
|
|
27
|
+
ret = {}
|
|
28
|
+
ret["id"] = repo.id
|
|
29
|
+
ret["name"] = repo.name
|
|
30
|
+
ret["full_name"] = repo.full_name
|
|
31
|
+
ret["owner"] = repo.owner.login
|
|
32
|
+
ret["private"] = repo.private
|
|
33
|
+
ret["visibility"] = repo.visibility
|
|
34
|
+
ret["html_url"] = repo.html_url
|
|
35
|
+
ret["clone_url"] = repo.clone_url
|
|
36
|
+
ret["ssh_url"] = repo.ssh_url
|
|
37
|
+
ret["description"] = repo.description
|
|
38
|
+
ret["fork"] = repo.fork
|
|
39
|
+
ret["archived"] = repo.archived
|
|
40
|
+
ret["disabled"] = repo.disabled
|
|
41
|
+
ret["is_template"] = repo.is_template
|
|
42
|
+
ret["homepage"] = repo.homepage
|
|
43
|
+
ret["license"] = repo.license.spdx_id if repo.license else None
|
|
44
|
+
ret["topics"] = repo.topics
|
|
45
|
+
ret["custom_properties"] = repo.custom_properties
|
|
46
|
+
ret["size"] = repo.size
|
|
47
|
+
ret["stargazers_count"] = repo.stargazers_count
|
|
48
|
+
ret["language"] = repo.language
|
|
49
|
+
ret["forks_count"] = repo.forks_count
|
|
50
|
+
ret["open_issues_count"] = repo.open_issues_count
|
|
51
|
+
ret["default_branch"] = repo.default_branch
|
|
52
|
+
ret["created_at"] = repo.created_at.isoformat() if repo.created_at else None
|
|
53
|
+
ret["updated_at"] = repo.updated_at.isoformat() if repo.updated_at else None
|
|
54
|
+
ret["pushed_at"] = repo.pushed_at.isoformat() if repo.pushed_at else None
|
|
55
|
+
ret["has_discussions"] = repo.has_discussions
|
|
56
|
+
ret["has_downloads"] = repo.has_downloads
|
|
57
|
+
ret["has_issues"] = repo.has_issues
|
|
58
|
+
ret["has_pages"] = repo.has_pages
|
|
59
|
+
ret["has_projects"] = repo.has_projects
|
|
60
|
+
ret["has_wiki"] = repo.has_wiki
|
|
61
|
+
return ret
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@api_request
|
|
65
|
+
def list_all(conn, *, owner=None, ignore_cache=False):
|
|
66
|
+
"""
|
|
67
|
+
Return all repositories of an owner as PyGithub ``Repository``
|
|
68
|
+
objects (cached).
|
|
69
|
+
|
|
70
|
+
conn
|
|
71
|
+
:class:`~saltext.github.utils.gh.client.GitHubConnection` to use.
|
|
72
|
+
|
|
73
|
+
owner
|
|
74
|
+
Owner whose repositories to list. Defaults to the connection's
|
|
75
|
+
owner. Private repositories of users other than the
|
|
76
|
+
authenticated one are not visible.
|
|
77
|
+
|
|
78
|
+
ignore_cache
|
|
79
|
+
Bypass the cached repository list.
|
|
80
|
+
"""
|
|
81
|
+
owner = owner or conn.owner
|
|
82
|
+
key = ("repos", owner.lower())
|
|
83
|
+
if key not in conn.cache or ignore_cache:
|
|
84
|
+
if conn.owner_type(owner) == "Organization":
|
|
85
|
+
found = conn.get_org(owner).get_repos()
|
|
86
|
+
elif conn.is_auth_user(owner):
|
|
87
|
+
# Listing the authenticated user's repos directly includes
|
|
88
|
+
# private ones, which /users/{owner}/repos does not carry.
|
|
89
|
+
found = conn.auth_user.get_repos(affiliation="owner")
|
|
90
|
+
else:
|
|
91
|
+
found = conn.client.get_user(owner).get_repos()
|
|
92
|
+
repos = []
|
|
93
|
+
for repo in found:
|
|
94
|
+
repos.append(repo)
|
|
95
|
+
# Cache a copy of each repo for single lookups
|
|
96
|
+
conn.cache[("repo_info", owner.lower(), repo.name.lower())] = to_dict(repo)
|
|
97
|
+
conn.cache[key] = repos
|
|
98
|
+
return conn.cache[key]
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@api_request
|
|
102
|
+
def get_info(conn, name, *, owner=None, ignore_cache=False):
|
|
103
|
+
"""
|
|
104
|
+
Return information about a single repository as a dictionary (cached).
|
|
105
|
+
|
|
106
|
+
conn
|
|
107
|
+
:class:`~saltext.github.utils.gh.client.GitHubConnection` to use.
|
|
108
|
+
|
|
109
|
+
name
|
|
110
|
+
Repository name.
|
|
111
|
+
|
|
112
|
+
owner
|
|
113
|
+
Owner of the repository. Defaults to the connection's owner.
|
|
114
|
+
|
|
115
|
+
ignore_cache
|
|
116
|
+
Bypass the cached repository information.
|
|
117
|
+
|
|
118
|
+
Raises :class:`~saltext.github.utils.gh.exceptions.GitHubNotFoundError`
|
|
119
|
+
if the repository does not exist.
|
|
120
|
+
"""
|
|
121
|
+
owner = owner or conn.owner
|
|
122
|
+
key = ("repo_info", owner.lower(), name.lower())
|
|
123
|
+
if key not in conn.cache or ignore_cache:
|
|
124
|
+
try:
|
|
125
|
+
# With lazy fetching, client.get_repo returns a Repository object
|
|
126
|
+
# even if the repo does not exist - the exception is only raised
|
|
127
|
+
# once attributes of the object are accessed.
|
|
128
|
+
repo = conn.client.get_repo(f"{owner}/{name}")
|
|
129
|
+
conn.cache[key] = to_dict(repo)
|
|
130
|
+
except UnknownObjectException as exc:
|
|
131
|
+
raise GitHubNotFoundError(
|
|
132
|
+
f"The '{name}' repository belonging to '{owner}' could not be found.",
|
|
133
|
+
status=exc.status,
|
|
134
|
+
data=exc.data,
|
|
135
|
+
) from exc
|
|
136
|
+
return conn.cache[key]
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def get_file_content(conn, name, path, *, owner=None, ignore_cache=False):
|
|
140
|
+
"""
|
|
141
|
+
Return the decoded text content of a repository file via the
|
|
142
|
+
contents API, or ``None`` when the file is missing or unusable
|
|
143
|
+
(not a regular file, too large for the API to carry inline, not
|
|
144
|
+
valid UTF-8). Results are cached, including negative ones – also
|
|
145
|
+
persistently when the connection carries a store, validated against
|
|
146
|
+
the repository's ``pushed_at`` timestamp.
|
|
147
|
+
|
|
148
|
+
conn
|
|
149
|
+
:class:`~saltext.github.utils.gh.client.GitHubConnection` to use.
|
|
150
|
+
|
|
151
|
+
name
|
|
152
|
+
Repository name.
|
|
153
|
+
|
|
154
|
+
path
|
|
155
|
+
Path of the file inside the repository.
|
|
156
|
+
|
|
157
|
+
owner
|
|
158
|
+
Owner of the repository. Defaults to the connection's owner.
|
|
159
|
+
|
|
160
|
+
ignore_cache
|
|
161
|
+
Bypass the cached file content.
|
|
162
|
+
"""
|
|
163
|
+
owner = owner or conn.owner
|
|
164
|
+
key = ("repo_file", owner.lower(), name.lower(), path)
|
|
165
|
+
if key in conn.cache and not ignore_cache:
|
|
166
|
+
return conn.cache[key]
|
|
167
|
+
content = MISS
|
|
168
|
+
pushed_at = None
|
|
169
|
+
if conn.store is not None:
|
|
170
|
+
pushed_at = _pushed_at(conn, name, owner)
|
|
171
|
+
if not ignore_cache:
|
|
172
|
+
content = conn.store.get_file(owner, name, path, pushed_at)
|
|
173
|
+
if content is MISS:
|
|
174
|
+
content = _fetch_file_content(conn, owner, name, path)
|
|
175
|
+
if conn.store is not None:
|
|
176
|
+
conn.store.set_file(owner, name, path, content, pushed_at)
|
|
177
|
+
conn.cache[key] = content
|
|
178
|
+
return content
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def _pushed_at(conn, name, owner):
|
|
182
|
+
"""
|
|
183
|
+
Return a repository's last-push timestamp (from the cached repository
|
|
184
|
+
information, usually pre-warmed by a listing), or ``None`` when it
|
|
185
|
+
cannot be determined.
|
|
186
|
+
"""
|
|
187
|
+
try:
|
|
188
|
+
return get_info(conn, name, owner=owner).get("pushed_at")
|
|
189
|
+
except CommandExecutionError:
|
|
190
|
+
return None
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def _fetch_file_content(conn, owner, name, path):
|
|
194
|
+
try:
|
|
195
|
+
res = conn.repo_query(owner, name, "contents", path)
|
|
196
|
+
except GitHubNotFoundError:
|
|
197
|
+
return None
|
|
198
|
+
if not isinstance(res, dict) or res.get("type") != "file":
|
|
199
|
+
log.warning("Path '%s' in %s/%s is not a file, skipping", path, owner, name)
|
|
200
|
+
return None
|
|
201
|
+
if res.get("encoding") != "base64":
|
|
202
|
+
log.warning(
|
|
203
|
+
"Cannot decode '%s' in %s/%s: unsupported encoding '%s'",
|
|
204
|
+
path,
|
|
205
|
+
owner,
|
|
206
|
+
name,
|
|
207
|
+
res.get("encoding"),
|
|
208
|
+
)
|
|
209
|
+
return None
|
|
210
|
+
try:
|
|
211
|
+
return base64.b64decode(res.get("content") or "").decode("utf-8")
|
|
212
|
+
except (ValueError, UnicodeDecodeError) as err:
|
|
213
|
+
log.warning("Failed decoding the content of '%s' in %s/%s: %s", path, owner, name, err)
|
|
214
|
+
return None
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def clear_file_cache(conn, name, *, owner=None):
|
|
218
|
+
"""
|
|
219
|
+
Drop all cached file contents of one repository, including cached
|
|
220
|
+
negative lookups and persistently stored ones.
|
|
221
|
+
|
|
222
|
+
conn
|
|
223
|
+
:class:`~saltext.github.utils.gh.client.GitHubConnection` to use.
|
|
224
|
+
|
|
225
|
+
name
|
|
226
|
+
Repository name.
|
|
227
|
+
|
|
228
|
+
owner
|
|
229
|
+
Owner of the repository. Defaults to the connection's owner.
|
|
230
|
+
"""
|
|
231
|
+
owner = owner or conn.owner
|
|
232
|
+
prefix = ("repo_file", owner.lower(), name.lower())
|
|
233
|
+
for key in [k for k in conn.cache if isinstance(k, tuple) and k[:3] == prefix]:
|
|
234
|
+
del conn.cache[key]
|
|
235
|
+
if conn.store is not None:
|
|
236
|
+
conn.store.flush_repo(owner, name)
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
@api_request
|
|
240
|
+
def get_teams(conn, name, *, owner=None):
|
|
241
|
+
"""
|
|
242
|
+
Return the teams a repository belongs to.
|
|
243
|
+
|
|
244
|
+
conn
|
|
245
|
+
:class:`~saltext.github.utils.gh.client.GitHubConnection` to use.
|
|
246
|
+
|
|
247
|
+
name
|
|
248
|
+
Repository name.
|
|
249
|
+
|
|
250
|
+
owner
|
|
251
|
+
Owner of the repository. Defaults to the connection's owner.
|
|
252
|
+
|
|
253
|
+
Raises :class:`~saltext.github.utils.gh.exceptions.GitHubNotFoundError`
|
|
254
|
+
if the repository does not exist.
|
|
255
|
+
"""
|
|
256
|
+
owner = owner or conn.owner
|
|
257
|
+
try:
|
|
258
|
+
repo = conn.client.get_repo(f"{owner}/{name}")
|
|
259
|
+
teams = repo.get_teams()
|
|
260
|
+
return [{"id": team.id, "name": team.name, "permission": team.permission} for team in teams]
|
|
261
|
+
except UnknownObjectException as exc:
|
|
262
|
+
raise GitHubNotFoundError(
|
|
263
|
+
f"The '{name}' repository belonging to '{owner}' could not be found.",
|
|
264
|
+
status=exc.status,
|
|
265
|
+
data=exc.data,
|
|
266
|
+
) from exc
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
@api_request
|
|
270
|
+
def create(
|
|
271
|
+
conn,
|
|
272
|
+
name,
|
|
273
|
+
*,
|
|
274
|
+
owner=None,
|
|
275
|
+
description=None,
|
|
276
|
+
homepage=None,
|
|
277
|
+
private=None,
|
|
278
|
+
has_issues=None,
|
|
279
|
+
has_wiki=None,
|
|
280
|
+
has_downloads=None,
|
|
281
|
+
auto_init=None,
|
|
282
|
+
gitignore_template=None,
|
|
283
|
+
license_template=None,
|
|
284
|
+
):
|
|
285
|
+
"""
|
|
286
|
+
Create a new repository and wait for it to become visible.
|
|
287
|
+
|
|
288
|
+
conn
|
|
289
|
+
:class:`~saltext.github.utils.gh.client.GitHubConnection` to use.
|
|
290
|
+
|
|
291
|
+
name
|
|
292
|
+
Repository name to create.
|
|
293
|
+
|
|
294
|
+
owner
|
|
295
|
+
Owner to create the repository for. Defaults to the connection's
|
|
296
|
+
owner. The GitHub API only supports creating repositories
|
|
297
|
+
for organizations and the authenticated user.
|
|
298
|
+
|
|
299
|
+
description
|
|
300
|
+
Repository description.
|
|
301
|
+
|
|
302
|
+
homepage
|
|
303
|
+
URL with more information about the repository, e.g. rendered docs.
|
|
304
|
+
|
|
305
|
+
private
|
|
306
|
+
Repository visibility.
|
|
307
|
+
|
|
308
|
+
has_issues
|
|
309
|
+
Enable issues for this repository.
|
|
310
|
+
|
|
311
|
+
has_wiki
|
|
312
|
+
Enable the wiki for this repository.
|
|
313
|
+
|
|
314
|
+
has_downloads
|
|
315
|
+
Enable downloads for this repository.
|
|
316
|
+
|
|
317
|
+
auto_init
|
|
318
|
+
Create an initial commit with an empty README.
|
|
319
|
+
|
|
320
|
+
gitignore_template
|
|
321
|
+
Desired language or platform for a .gitignore, e.g. "Haskell".
|
|
322
|
+
|
|
323
|
+
license_template
|
|
324
|
+
Desired LICENSE template to apply, e.g. "mit" or "mozilla".
|
|
325
|
+
"""
|
|
326
|
+
parameters = filter_unset(
|
|
327
|
+
{
|
|
328
|
+
"description": description,
|
|
329
|
+
"homepage": homepage,
|
|
330
|
+
"private": private,
|
|
331
|
+
"has_issues": has_issues,
|
|
332
|
+
"has_wiki": has_wiki,
|
|
333
|
+
"has_downloads": has_downloads,
|
|
334
|
+
"auto_init": auto_init,
|
|
335
|
+
"gitignore_template": gitignore_template,
|
|
336
|
+
"license_template": license_template,
|
|
337
|
+
}
|
|
338
|
+
)
|
|
339
|
+
owner = owner or conn.owner
|
|
340
|
+
if conn.owner_type(owner) == "Organization":
|
|
341
|
+
creator = conn.get_org(owner)
|
|
342
|
+
elif conn.is_auth_user(owner):
|
|
343
|
+
creator = conn.auth_user
|
|
344
|
+
else:
|
|
345
|
+
raise CommandExecutionError(
|
|
346
|
+
f"Cannot create a repository for '{owner}': the GitHub API only supports "
|
|
347
|
+
"creating repositories for organizations and the authenticated user."
|
|
348
|
+
)
|
|
349
|
+
repo = creator.create_repo(name, **parameters)
|
|
350
|
+
|
|
351
|
+
# Fetching a new repo's associations can 404 for a short while
|
|
352
|
+
# after creation, wait until it settles. User repos have no team
|
|
353
|
+
# associations, probe the repo itself there.
|
|
354
|
+
probe = get_teams if creator is not conn.auth_user else get_info
|
|
355
|
+
for attempt in range(3):
|
|
356
|
+
try:
|
|
357
|
+
probe(conn, name, owner=owner)
|
|
358
|
+
break
|
|
359
|
+
except GitHubNotFoundError:
|
|
360
|
+
log.info("Attempt %s to fetch new repo %s failed", attempt, name)
|
|
361
|
+
time.sleep(1)
|
|
362
|
+
else:
|
|
363
|
+
raise CommandExecutionError(f"Failed to verify repo {name} after creation.")
|
|
364
|
+
|
|
365
|
+
info = to_dict(repo)
|
|
366
|
+
conn.cache[("repo_info", owner.lower(), repo.name.lower())] = info
|
|
367
|
+
if ("repos", owner.lower()) in conn.cache:
|
|
368
|
+
conn.cache[("repos", owner.lower())].append(repo)
|
|
369
|
+
return info
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
@api_request
|
|
373
|
+
def update(
|
|
374
|
+
conn,
|
|
375
|
+
name,
|
|
376
|
+
*,
|
|
377
|
+
owner=None,
|
|
378
|
+
description=None,
|
|
379
|
+
homepage=None,
|
|
380
|
+
private=None,
|
|
381
|
+
has_issues=None,
|
|
382
|
+
has_wiki=None,
|
|
383
|
+
has_downloads=None,
|
|
384
|
+
default_branch=None,
|
|
385
|
+
allow_privacy_change=False,
|
|
386
|
+
):
|
|
387
|
+
"""
|
|
388
|
+
Update an existing repository.
|
|
389
|
+
|
|
390
|
+
conn
|
|
391
|
+
:class:`~saltext.github.utils.gh.client.GitHubConnection` to use.
|
|
392
|
+
|
|
393
|
+
name
|
|
394
|
+
Repository name to update.
|
|
395
|
+
|
|
396
|
+
owner
|
|
397
|
+
Owner of the repository. Defaults to the connection's owner.
|
|
398
|
+
|
|
399
|
+
description
|
|
400
|
+
Repository description.
|
|
401
|
+
|
|
402
|
+
homepage
|
|
403
|
+
URL with more information about the repository, e.g. rendered docs.
|
|
404
|
+
|
|
405
|
+
private
|
|
406
|
+
Repository visibility. Changing this requires :ghconf:`allow_repo_privacy_changes`
|
|
407
|
+
to be enabled in the profile.
|
|
408
|
+
|
|
409
|
+
has_issues
|
|
410
|
+
Enable issues for this repository.
|
|
411
|
+
|
|
412
|
+
has_wiki
|
|
413
|
+
Enable the wiki for this repository.
|
|
414
|
+
|
|
415
|
+
has_downloads
|
|
416
|
+
Enable downloads for this repository.
|
|
417
|
+
|
|
418
|
+
default_branch
|
|
419
|
+
Default branch of the repository. Must already exist: Ignored during repo creation.
|
|
420
|
+
|
|
421
|
+
allow_privacy_change
|
|
422
|
+
Allow changing the repository's visibility. Defaults to false.
|
|
423
|
+
"""
|
|
424
|
+
if private is not None and not allow_privacy_change:
|
|
425
|
+
raise CommandExecutionError(
|
|
426
|
+
f"The private field is set to be changed for repo {name} "
|
|
427
|
+
"but allow_repo_privacy_changes disallows this."
|
|
428
|
+
)
|
|
429
|
+
owner = owner or conn.owner
|
|
430
|
+
repo = conn.client.get_repo(f"{owner}/{name}")
|
|
431
|
+
parameters = filter_unset(
|
|
432
|
+
{
|
|
433
|
+
"description": description,
|
|
434
|
+
"homepage": homepage,
|
|
435
|
+
"private": private,
|
|
436
|
+
"has_issues": has_issues,
|
|
437
|
+
"has_wiki": has_wiki,
|
|
438
|
+
"has_downloads": has_downloads,
|
|
439
|
+
"default_branch": default_branch,
|
|
440
|
+
}
|
|
441
|
+
)
|
|
442
|
+
repo.edit(name, **parameters)
|
|
443
|
+
return get_info(conn, name, owner=owner, ignore_cache=True) # Refresh cache
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
@api_request
|
|
447
|
+
def delete(conn, name, *, owner=None):
|
|
448
|
+
"""
|
|
449
|
+
Delete a repository.
|
|
450
|
+
|
|
451
|
+
conn
|
|
452
|
+
:class:`~saltext.github.utils.gh.client.GitHubConnection` to use.
|
|
453
|
+
|
|
454
|
+
name
|
|
455
|
+
Repository name to delete.
|
|
456
|
+
|
|
457
|
+
owner
|
|
458
|
+
Owner of the repository. Defaults to the connection's owner.
|
|
459
|
+
"""
|
|
460
|
+
owner = owner or conn.owner
|
|
461
|
+
repo = conn.client.get_repo(f"{owner}/{name}")
|
|
462
|
+
repo.delete()
|
|
463
|
+
conn.cache.pop(("repo_info", owner.lower(), name.lower()), None)
|
|
464
|
+
if ("repos", owner.lower()) in conn.cache:
|
|
465
|
+
list_all(conn, owner=owner, ignore_cache=True) # Refresh the repo list cache
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Shared plumbing for the :py:mod:`github <saltext.github.resources.github>`
|
|
3
|
+
resource type's execution and state modules.
|
|
4
|
+
|
|
5
|
+
The functions here reference loader globals (``__resource__``,
|
|
6
|
+
``__salt__``, ``__opts__``, ``__context__``), so consuming modules must
|
|
7
|
+
rebind them to their own namespace via
|
|
8
|
+
:func:`salt.utils.functools.namespaced_function`::
|
|
9
|
+
|
|
10
|
+
from salt.utils.functools import namespaced_function
|
|
11
|
+
|
|
12
|
+
from saltext.github.utils.gh import resource as gh_resource
|
|
13
|
+
|
|
14
|
+
_core_args = namespaced_function(gh_resource.core_args, globals())
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from saltext.github.utils.gh import profile as gh_profile
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def core_args(profile=None):
|
|
21
|
+
"""
|
|
22
|
+
Return a connection for the targeted repository, along with the
|
|
23
|
+
repository's owner and name.
|
|
24
|
+
|
|
25
|
+
Salt's authoring docs promise ``__resource__`` inside state
|
|
26
|
+
overrides, but no released Salt packs it there — in state modules,
|
|
27
|
+
fall back to the ``repo.ident`` execution override, which does see it.
|
|
28
|
+
"""
|
|
29
|
+
try:
|
|
30
|
+
rid = __resource__["id"]
|
|
31
|
+
except NameError:
|
|
32
|
+
rid = __salt__["repo.ident"]()["id"]
|
|
33
|
+
owner, _, repo_name = rid.partition("/")
|
|
34
|
+
conn = gh_profile.resource_conn(__opts__, __context__, profile=profile, owner=owner)
|
|
35
|
+
return conn, owner, repo_name
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Operations on GitHub repository and organization rulesets.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
|
|
7
|
+
from salt.exceptions import CommandExecutionError
|
|
8
|
+
|
|
9
|
+
log = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def build_path(ruleset_type, *, owner=None, repo_name=None, org_name=None, ruleset_id=None):
|
|
13
|
+
"""
|
|
14
|
+
Build the API path for ruleset operations.
|
|
15
|
+
|
|
16
|
+
ruleset_type
|
|
17
|
+
Ruleset type, either ``repo`` or ``org``.
|
|
18
|
+
|
|
19
|
+
owner
|
|
20
|
+
Owner of the repository. Required for ``repo`` rulesets
|
|
21
|
+
(the calling functions default it to the connection's owner).
|
|
22
|
+
|
|
23
|
+
repo_name
|
|
24
|
+
Repo name. Required for ``repo`` rulesets.
|
|
25
|
+
|
|
26
|
+
org_name
|
|
27
|
+
Org name. Required for ``org`` rulesets (the calling functions
|
|
28
|
+
default it to the connection's owner).
|
|
29
|
+
|
|
30
|
+
ruleset_id
|
|
31
|
+
ID of a specific ruleset to address.
|
|
32
|
+
"""
|
|
33
|
+
if ruleset_type == "repo":
|
|
34
|
+
path = ["repos", owner, repo_name, "rulesets"]
|
|
35
|
+
elif ruleset_type == "org":
|
|
36
|
+
path = ["orgs", org_name, "rulesets"]
|
|
37
|
+
else:
|
|
38
|
+
raise CommandExecutionError(f"Invalid ruleset type: {ruleset_type}")
|
|
39
|
+
if ruleset_id is not None:
|
|
40
|
+
path.append(ruleset_id)
|
|
41
|
+
return path
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def check_params(ruleset_params, required=None):
|
|
45
|
+
"""
|
|
46
|
+
Ensure a ruleset parameter payload is a mapping and contains all
|
|
47
|
+
required keys.
|
|
48
|
+
|
|
49
|
+
ruleset_params
|
|
50
|
+
Payload to check.
|
|
51
|
+
|
|
52
|
+
required
|
|
53
|
+
Optional set of parameter names that must be present.
|
|
54
|
+
"""
|
|
55
|
+
if not isinstance(ruleset_params, dict):
|
|
56
|
+
raise CommandExecutionError("ruleset_params need to be a dict")
|
|
57
|
+
if required and (missing := set(required).difference(ruleset_params)):
|
|
58
|
+
raise CommandExecutionError(
|
|
59
|
+
f"The following ruleset_params are required, but not defined: {', '.join(missing)}"
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def list_all(
|
|
64
|
+
conn, ruleset_type, *, owner=None, repo_name=None, org_name=None, headers=None, per_page=None
|
|
65
|
+
):
|
|
66
|
+
"""
|
|
67
|
+
Return all rulesets of a repository or organization.
|
|
68
|
+
|
|
69
|
+
conn
|
|
70
|
+
:class:`~saltext.github.utils.gh.client.GitHubConnection` to use.
|
|
71
|
+
|
|
72
|
+
Other parameters as described in :func:`build_path`. ``headers`` is a
|
|
73
|
+
mapping of extra headers to send.
|
|
74
|
+
"""
|
|
75
|
+
path = build_path(
|
|
76
|
+
ruleset_type,
|
|
77
|
+
owner=owner or conn.owner,
|
|
78
|
+
repo_name=repo_name,
|
|
79
|
+
org_name=org_name or conn.owner,
|
|
80
|
+
)
|
|
81
|
+
return conn.query(path, headers=headers, per_page=per_page)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def get(conn, ruleset_id, ruleset_type, *, owner=None, repo_name=None, org_name=None, headers=None):
|
|
85
|
+
"""
|
|
86
|
+
Return the data of a specific ruleset.
|
|
87
|
+
|
|
88
|
+
conn
|
|
89
|
+
:class:`~saltext.github.utils.gh.client.GitHubConnection` to use.
|
|
90
|
+
|
|
91
|
+
ruleset_id
|
|
92
|
+
ID of the ruleset to retrieve.
|
|
93
|
+
|
|
94
|
+
Other parameters as described in :func:`build_path`. ``headers`` is a
|
|
95
|
+
mapping of extra headers to send.
|
|
96
|
+
"""
|
|
97
|
+
path = build_path(
|
|
98
|
+
ruleset_type,
|
|
99
|
+
owner=owner or conn.owner,
|
|
100
|
+
repo_name=repo_name,
|
|
101
|
+
org_name=org_name or conn.owner,
|
|
102
|
+
ruleset_id=ruleset_id,
|
|
103
|
+
)
|
|
104
|
+
return conn.query(path, headers=headers)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def create(
|
|
108
|
+
conn, ruleset_params, ruleset_type, *, owner=None, repo_name=None, org_name=None, headers=None
|
|
109
|
+
):
|
|
110
|
+
"""
|
|
111
|
+
Create a new ruleset. Returns the data of the created ruleset.
|
|
112
|
+
|
|
113
|
+
conn
|
|
114
|
+
:class:`~saltext.github.utils.gh.client.GitHubConnection` to use.
|
|
115
|
+
|
|
116
|
+
ruleset_params
|
|
117
|
+
Ruleset definition. Must contain at least ``name`` and ``enforcement``.
|
|
118
|
+
|
|
119
|
+
Other parameters as described in :func:`build_path`. ``headers`` is a
|
|
120
|
+
mapping of extra headers to send.
|
|
121
|
+
"""
|
|
122
|
+
check_params(ruleset_params, required={"name", "enforcement"})
|
|
123
|
+
path = build_path(
|
|
124
|
+
ruleset_type,
|
|
125
|
+
owner=owner or conn.owner,
|
|
126
|
+
repo_name=repo_name,
|
|
127
|
+
org_name=org_name or conn.owner,
|
|
128
|
+
)
|
|
129
|
+
return conn.query(path, method="POST", data=ruleset_params, headers=headers)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def update(
|
|
133
|
+
conn,
|
|
134
|
+
ruleset_id,
|
|
135
|
+
ruleset_params,
|
|
136
|
+
ruleset_type,
|
|
137
|
+
*,
|
|
138
|
+
owner=None,
|
|
139
|
+
repo_name=None,
|
|
140
|
+
org_name=None,
|
|
141
|
+
headers=None,
|
|
142
|
+
):
|
|
143
|
+
"""
|
|
144
|
+
Update an existing ruleset. Returns the data of the updated ruleset.
|
|
145
|
+
|
|
146
|
+
conn
|
|
147
|
+
:class:`~saltext.github.utils.gh.client.GitHubConnection` to use.
|
|
148
|
+
|
|
149
|
+
ruleset_id
|
|
150
|
+
ID of the ruleset to update.
|
|
151
|
+
|
|
152
|
+
ruleset_params
|
|
153
|
+
Updated ruleset definition.
|
|
154
|
+
|
|
155
|
+
Other parameters as described in :func:`build_path`. ``headers`` is a
|
|
156
|
+
mapping of extra headers to send.
|
|
157
|
+
"""
|
|
158
|
+
check_params(ruleset_params)
|
|
159
|
+
path = build_path(
|
|
160
|
+
ruleset_type,
|
|
161
|
+
owner=owner or conn.owner,
|
|
162
|
+
repo_name=repo_name,
|
|
163
|
+
org_name=org_name or conn.owner,
|
|
164
|
+
ruleset_id=ruleset_id,
|
|
165
|
+
)
|
|
166
|
+
return conn.query(path, method="PUT", data=ruleset_params, headers=headers)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def delete(
|
|
170
|
+
conn, ruleset_id, ruleset_type, *, owner=None, repo_name=None, org_name=None, headers=None
|
|
171
|
+
):
|
|
172
|
+
"""
|
|
173
|
+
Delete a ruleset.
|
|
174
|
+
|
|
175
|
+
conn
|
|
176
|
+
:class:`~saltext.github.utils.gh.client.GitHubConnection` to use.
|
|
177
|
+
|
|
178
|
+
ruleset_id
|
|
179
|
+
ID of the ruleset to delete.
|
|
180
|
+
|
|
181
|
+
Other parameters as described in :func:`build_path`. ``headers`` is a
|
|
182
|
+
mapping of extra headers to send.
|
|
183
|
+
"""
|
|
184
|
+
path = build_path(
|
|
185
|
+
ruleset_type,
|
|
186
|
+
owner=owner or conn.owner,
|
|
187
|
+
repo_name=repo_name,
|
|
188
|
+
org_name=org_name or conn.owner,
|
|
189
|
+
ruleset_id=ruleset_id,
|
|
190
|
+
)
|
|
191
|
+
conn.query(path, method="DELETE", headers=headers)
|