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.
Files changed (73) hide show
  1. saltext/github/__init__.py +31 -0
  2. saltext/github/modules/__init__.py +0 -0
  3. saltext/github/modules/github.py +1873 -0
  4. saltext/github/modules/github_org.py +667 -0
  5. saltext/github/modules/github_repo.py +1658 -0
  6. saltext/github/modules/github_user.py +75 -0
  7. saltext/github/resources/__init__.py +0 -0
  8. saltext/github/resources/github/__init__.py +251 -0
  9. saltext/github/resources/github/modules/__init__.py +0 -0
  10. saltext/github/resources/github/modules/actions.py +175 -0
  11. saltext/github/resources/github/modules/config.py +69 -0
  12. saltext/github/resources/github/modules/deploy_key.py +122 -0
  13. saltext/github/resources/github/modules/environment.py +241 -0
  14. saltext/github/resources/github/modules/grains.py +18 -0
  15. saltext/github/resources/github/modules/issue.py +157 -0
  16. saltext/github/resources/github/modules/label.py +161 -0
  17. saltext/github/resources/github/modules/milestone.py +103 -0
  18. saltext/github/resources/github/modules/pages.py +149 -0
  19. saltext/github/resources/github/modules/pull.py +90 -0
  20. saltext/github/resources/github/modules/repo.py +155 -0
  21. saltext/github/resources/github/modules/ruleset.py +144 -0
  22. saltext/github/resources/github/modules/saltutil.py +24 -0
  23. saltext/github/resources/github/modules/state.py +116 -0
  24. saltext/github/resources/github/modules/test.py +29 -0
  25. saltext/github/resources/github/states/__init__.py +0 -0
  26. saltext/github/resources/github/states/actions.py +117 -0
  27. saltext/github/resources/github/states/deploy_key.py +130 -0
  28. saltext/github/resources/github/states/environment.py +99 -0
  29. saltext/github/resources/github/states/label.py +71 -0
  30. saltext/github/resources/github/states/pages.py +92 -0
  31. saltext/github/resources/github/states/repo.py +108 -0
  32. saltext/github/resources/github/states/ruleset.py +81 -0
  33. saltext/github/states/__init__.py +0 -0
  34. saltext/github/states/github.py +966 -0
  35. saltext/github/states/github_org.py +369 -0
  36. saltext/github/states/github_repo.py +722 -0
  37. saltext/github/utils/__init__.py +72 -0
  38. saltext/github/utils/gh/__init__.py +14 -0
  39. saltext/github/utils/gh/actions.py +195 -0
  40. saltext/github/utils/gh/cache.py +126 -0
  41. saltext/github/utils/gh/client.py +534 -0
  42. saltext/github/utils/gh/deploy_keys.py +121 -0
  43. saltext/github/utils/gh/environments.py +271 -0
  44. saltext/github/utils/gh/exceptions.py +28 -0
  45. saltext/github/utils/gh/grains_files.py +366 -0
  46. saltext/github/utils/gh/issues.py +193 -0
  47. saltext/github/utils/gh/labels.py +142 -0
  48. saltext/github/utils/gh/members.py +157 -0
  49. saltext/github/utils/gh/milestones.py +122 -0
  50. saltext/github/utils/gh/pages.py +128 -0
  51. saltext/github/utils/gh/profile.py +458 -0
  52. saltext/github/utils/gh/pulls.py +99 -0
  53. saltext/github/utils/gh/repos.py +465 -0
  54. saltext/github/utils/gh/resource.py +35 -0
  55. saltext/github/utils/gh/rulesets.py +191 -0
  56. saltext/github/utils/gh/statecores/__init__.py +70 -0
  57. saltext/github/utils/gh/statecores/actions.py +180 -0
  58. saltext/github/utils/gh/statecores/deploy_keys.py +241 -0
  59. saltext/github/utils/gh/statecores/environments.py +212 -0
  60. saltext/github/utils/gh/statecores/labels.py +116 -0
  61. saltext/github/utils/gh/statecores/pages.py +177 -0
  62. saltext/github/utils/gh/statecores/repo.py +249 -0
  63. saltext/github/utils/gh/statecores/rulesets.py +115 -0
  64. saltext/github/utils/gh/teams.py +375 -0
  65. saltext/github/utils/gh/users.py +87 -0
  66. saltext/github/version.py +1 -0
  67. saltext_github-1.1.0.dist-info/METADATA +151 -0
  68. saltext_github-1.1.0.dist-info/RECORD +73 -0
  69. saltext_github-1.1.0.dist-info/WHEEL +5 -0
  70. saltext_github-1.1.0.dist-info/entry_points.txt +2 -0
  71. saltext_github-1.1.0.dist-info/licenses/LICENSE +201 -0
  72. saltext_github-1.1.0.dist-info/licenses/NOTICE +10 -0
  73. 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)