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,966 @@
1
+ """
2
+ **Deprecated**: Manage organization teams/members, repositories and rulesets.
3
+
4
+ .. deprecated:: 1.1.0
5
+
6
+ Use the :py:mod:`github_org <saltext.github.states.github_org>` and
7
+ :py:mod:`github_repo <saltext.github.states.github_repo>` state modules
8
+ instead.
9
+ """
10
+
11
+ import datetime
12
+ import logging
13
+ import time
14
+
15
+ from salt.exceptions import CommandExecutionError
16
+
17
+ from saltext.github.utils.gh.statecores import catch_errors as _catch_errors
18
+
19
+ log = logging.getLogger(__name__)
20
+
21
+ __virtualname__ = "github"
22
+
23
+
24
+ def __virtual__():
25
+ """
26
+ Only load if the github module is available in __salt__
27
+ """
28
+ if "github.list_users" in __salt__:
29
+ return __virtualname__
30
+ return (False, "github module could not be loaded") # pragma: no cover
31
+
32
+
33
+ @_catch_errors
34
+ def present(name, profile="github", **kwargs):
35
+ """
36
+ Ensure a user is present
37
+
38
+ .. deprecated:: 1.1.0
39
+ Use :py:func:`github_org.member_present <saltext.github.states.github_org.member_present>` instead.
40
+
41
+ .. code-block:: yaml
42
+
43
+ ensure user test is present in github:
44
+ github.present:
45
+ - name: gitexample
46
+
47
+ name
48
+ This is the github handle of the user in the organization
49
+ """
50
+
51
+ ret = {"name": name, "changes": {}, "result": None, "comment": ""}
52
+
53
+ target = __salt__["github.get_user"](name, profile=profile, **kwargs)
54
+
55
+ # If the user has a valid github handle and is not in the org already
56
+ if not target:
57
+ ret["result"] = False
58
+ ret["comment"] = f"Couldnt find user {name}"
59
+ elif isinstance(target, bool) and target:
60
+ ret["comment"] = f"User {name} is already in the org "
61
+ ret["result"] = True
62
+ elif not target.get("in_org", False) and target.get("membership_state") != "pending":
63
+ if __opts__["test"]:
64
+ ret["comment"] = f"User {name} will be added to the org"
65
+ ret["changes"]["old"] = None
66
+ ret["changes"]["new"] = f"User {name} exists in the org now"
67
+ return ret
68
+
69
+ # add the user
70
+ result = __salt__["github.add_user"](name, profile=profile, **kwargs)
71
+
72
+ if result:
73
+ ret["changes"].setdefault("old", None)
74
+ ret["changes"].setdefault("new", f"User {name} exists in the org now")
75
+ ret["result"] = True
76
+ else:
77
+ ret["result"] = False
78
+ ret["comment"] = f"Failed to add user {name} to the org"
79
+ else:
80
+ ret["comment"] = f"User {name} has already been invited."
81
+ ret["result"] = True
82
+
83
+ return ret
84
+
85
+
86
+ @_catch_errors
87
+ def absent(name, profile="github", **kwargs):
88
+ """
89
+ Ensure a github user is absent
90
+
91
+ .. deprecated:: 1.1.0
92
+ Use :py:func:`github_org.member_absent <saltext.github.states.github_org.member_absent>` instead.
93
+
94
+ .. code-block:: yaml
95
+
96
+ ensure user test is absent in github:
97
+ github.absent:
98
+ - name: Example TestUser1
99
+ - email: example@domain.com
100
+ - username: gitexample
101
+
102
+ name
103
+ Github handle of the user in organization
104
+ """
105
+
106
+ ret = {
107
+ "name": name,
108
+ "changes": {},
109
+ "result": None,
110
+ "comment": f"User {name} is absent.",
111
+ }
112
+
113
+ target = __salt__["github.get_user"](name, profile=profile, **kwargs)
114
+
115
+ if target:
116
+ if isinstance(target, bool) or target.get("in_org", False):
117
+ if __opts__["test"]:
118
+ ret["comment"] = f"User {name} will be deleted"
119
+ ret["changes"]["old"] = f"User {name} exists"
120
+ ret["changes"]["new"] = f"User {name} deleted"
121
+ ret["result"] = None
122
+ return ret
123
+
124
+ result = __salt__["github.remove_user"](name, profile=profile, **kwargs)
125
+
126
+ if result:
127
+ ret["comment"] = f"Deleted user {name}"
128
+ ret["changes"].setdefault("old", f"User {name} exists")
129
+ ret["changes"].setdefault("new", f"User {name} deleted")
130
+ ret["result"] = True
131
+ else:
132
+ ret["comment"] = f"Failed to delete {name}"
133
+ ret["result"] = False
134
+ else:
135
+ ret["comment"] = f"User {name} has already been deleted!"
136
+ ret["result"] = True
137
+ else:
138
+ ret["comment"] = f"User {name} does not exist"
139
+ ret["result"] = True
140
+ return ret
141
+
142
+ return ret
143
+
144
+
145
+ @_catch_errors
146
+ def team_present( # pylint: disable=too-many-statements
147
+ name,
148
+ description=None,
149
+ repo_names=None,
150
+ privacy="secret",
151
+ permission="pull",
152
+ members=None,
153
+ enforce_mfa=False,
154
+ no_mfa_grace_seconds=0,
155
+ profile="github",
156
+ **kwargs,
157
+ ):
158
+ """
159
+ Ensure a team is present
160
+
161
+ .. deprecated:: 1.1.0
162
+ Use :py:func:`github_org.team_present <saltext.github.states.github_org.team_present>` instead.
163
+
164
+ name
165
+ This is the name of the team in the organization.
166
+
167
+ description
168
+ The description of the team.
169
+
170
+ repo_names
171
+ The names of repositories to add the team to.
172
+
173
+ privacy
174
+ The level of privacy for the team, can be ``secret`` or ``closed``. Defaults
175
+ to secret.
176
+
177
+ permission
178
+ The default permission for new repositories added to the team, can be
179
+ ``pull``, ``push`` or ``admin``. Defaults to pull.
180
+
181
+ members
182
+ The members belonging to the team, specified as a dict of member name to
183
+ optional configuration. Options include ``enforce_mfa_from`` and ``mfa_exempt``.
184
+
185
+ enforce_mfa
186
+ Whether to enforce MFA requirements on members of the team. If True then
187
+ all members without ``mfa_exempt: True`` configured will be removed from
188
+ the team. Note that ``no_mfa_grace_seconds`` may be set to allow members
189
+ a grace period.
190
+
191
+ no_mfa_grace_seconds
192
+ The number of seconds of grace time that a member will have to enable MFA
193
+ before being removed from the team. The grace period will begin from
194
+ ``enforce_mfa_from`` on the member configuration, which defaults to
195
+ 1970/01/01.
196
+
197
+ Example:
198
+
199
+ .. code-block:: yaml
200
+
201
+ Ensure team test is present in github:
202
+ github.team_present:
203
+ - name: test
204
+ - members:
205
+ user1: {}
206
+ user2: {}
207
+
208
+ Ensure team test_mfa is present in github:
209
+ github.team_present:
210
+ - name: test_mfa
211
+ - members:
212
+ user1:
213
+ enforce_mfa_from: 2016/06/15
214
+ - enforce_mfa: True
215
+ """
216
+ ret = {"name": name, "changes": {}, "result": True, "comment": ""}
217
+
218
+ target = __salt__["github.get_team"](name, profile=profile, **kwargs)
219
+ test_comments = []
220
+
221
+ if target: # Team already exists
222
+ parameters = {}
223
+ if description is not None and target["description"] != description:
224
+ parameters["description"] = description
225
+ if permission is not None and target["permission"] != permission:
226
+ parameters["permission"] = permission
227
+ if privacy is not None and target["privacy"] != privacy:
228
+ parameters["privacy"] = privacy
229
+
230
+ if len(parameters) > 0:
231
+ team_change = {
232
+ "old": f"Team properties were {target}",
233
+ "new": f"Team properties (that changed) are {parameters}",
234
+ }
235
+ if __opts__["test"]:
236
+ test_comments.append(f"Team properties are set to be edited: {parameters}")
237
+ ret["result"] = None
238
+ ret["changes"]["team"] = team_change
239
+ else:
240
+ result = __salt__["github.edit_team"](name, profile=profile, **parameters)
241
+ if result:
242
+ ret["changes"]["team"] = team_change
243
+ else:
244
+ ret["result"] = False
245
+ ret["comment"] = "Failed to update team properties."
246
+ return ret
247
+
248
+ current_repos = set(__salt__["github.list_team_repos"](name, profile=profile))
249
+ repo_names = set(repo_names or [])
250
+
251
+ repos_to_add = repo_names - current_repos
252
+ repos_to_remove = current_repos - repo_names if repo_names else []
253
+
254
+ if repos_to_add:
255
+ if __opts__["test"]:
256
+ test_comments.append(
257
+ "Team {} will have the following repos added: {}.".format( # pylint: disable=consider-using-f-string
258
+ name, list(repos_to_add)
259
+ )
260
+ )
261
+ ret["result"] = None
262
+ for repo_name in repos_to_add:
263
+ repo_change = {
264
+ "old": f"Repo {repo_name} is not in team {name}",
265
+ "new": f"Repo {repo_name} is in team {name}",
266
+ }
267
+ if __opts__["test"]:
268
+ ret["changes"][repo_name] = repo_change
269
+ else:
270
+ result = __salt__["github.add_team_repo"](
271
+ repo_name, name, profile=profile, **kwargs
272
+ )
273
+ if result:
274
+ ret["changes"][repo_name] = repo_change
275
+ else:
276
+ ret["result"] = False
277
+ ret["comment"] = f"Failed to add repo {repo_name} to team {name}."
278
+ return ret
279
+
280
+ if repos_to_remove:
281
+ if __opts__["test"]:
282
+ test_comments.append(
283
+ "Team {} will have the following repos removed: {}.".format( # pylint: disable=consider-using-f-string
284
+ name, list(repos_to_remove)
285
+ )
286
+ )
287
+ ret["result"] = None
288
+ for repo_name in repos_to_remove:
289
+ repo_change = {
290
+ "old": f"Repo {repo_name} is in team {name}",
291
+ "new": f"Repo {repo_name} is not in team {name}",
292
+ }
293
+ if __opts__["test"]:
294
+ ret["changes"][repo_name] = repo_change
295
+ else:
296
+ result = __salt__["github.remove_team_repo"](
297
+ repo_name, name, profile=profile, **kwargs
298
+ )
299
+ if result:
300
+ ret["changes"][repo_name] = repo_change
301
+ else:
302
+ ret["result"] = False
303
+ ret["comment"] = f"Failed to remove repo {repo_name} from team {name}."
304
+ return ret
305
+
306
+ else: # Team does not exist - it will be created.
307
+ if __opts__["test"]:
308
+ # Note: member/repo changes are not projected for new teams
309
+ ret["comment"] = f"Team {name} is set to be created."
310
+ ret["changes"]["team"] = {"old": None, "new": f"Team {name} has been created"}
311
+ ret["result"] = None
312
+ return ret
313
+
314
+ result = __salt__["github.add_team"](
315
+ name,
316
+ description=description,
317
+ repo_names=repo_names,
318
+ permission=permission,
319
+ privacy=privacy,
320
+ profile=profile,
321
+ **kwargs,
322
+ )
323
+ if result:
324
+ ret["changes"]["team"] = {}
325
+ ret["changes"]["team"]["old"] = None
326
+ ret["changes"]["team"]["new"] = f"Team {name} has been created"
327
+ else:
328
+ ret["result"] = False
329
+ ret["comment"] = f"Failed to create team {name}."
330
+ return ret
331
+
332
+ manage_members = members is not None
333
+
334
+ mfa_deadline = datetime.datetime.now(tz=datetime.UTC) - datetime.timedelta(
335
+ seconds=no_mfa_grace_seconds
336
+ )
337
+ members_no_mfa = __salt__["github.list_members_without_mfa"](profile=profile)
338
+
339
+ members_lower = {}
340
+ for member_name, info in (members or {}).items():
341
+ members_lower[member_name.lower()] = info
342
+
343
+ member_change = False
344
+ current_members = __salt__["github.list_team_members"](name, profile=profile)
345
+
346
+ for member, member_info in (members or {}).items():
347
+ log.info("Checking member %s in team %s", member, name)
348
+
349
+ if member.lower() not in current_members:
350
+ if enforce_mfa and _member_violates_mfa(
351
+ member, member_info, mfa_deadline, members_no_mfa
352
+ ):
353
+ if __opts__["test"]:
354
+ test_comments.append(
355
+ f"User {member} will not be added to the team because they do not have MFA."
356
+ )
357
+ else: # Add to team
358
+ member_change = True
359
+ member_add = {
360
+ "old": f"User {member} is not in team {name}",
361
+ "new": f"User {member} is in team {name}",
362
+ }
363
+ if __opts__["test"]:
364
+ test_comments.append(f"User {member} set to be added to the team.")
365
+ ret["result"] = None
366
+ ret["changes"][member] = member_add
367
+ else:
368
+ result = __salt__["github.add_team_member"](
369
+ member, name, profile=profile, **kwargs
370
+ )
371
+ if result:
372
+ ret["changes"][member] = member_add
373
+ else:
374
+ ret["result"] = False
375
+ ret["comment"] = f"Failed to add user {member} to team {name}."
376
+ return ret
377
+
378
+ for member in current_members:
379
+ mfa_violation = False
380
+ if member in members_lower:
381
+ mfa_violation = _member_violates_mfa(
382
+ member, members_lower[member], mfa_deadline, members_no_mfa
383
+ )
384
+ if manage_members and member not in members_lower or (enforce_mfa and mfa_violation):
385
+ # Remove from team
386
+ member_change = True
387
+ extra_changes = " due to MFA violation" if mfa_violation else ""
388
+ member_removal = {
389
+ "old": f"User {member} is in team {name}",
390
+ "new": f"User {member} is not in team {name}{extra_changes}",
391
+ }
392
+ if __opts__["test"]:
393
+ if mfa_violation:
394
+ test_comments.append(
395
+ f"User {member} set to be removed from the team because they do not have MFA."
396
+ )
397
+ else:
398
+ test_comments.append(f"User {member} set to be removed from the team.")
399
+ ret["result"] = None
400
+ ret["changes"][member] = member_removal
401
+ else:
402
+ result = __salt__["github.remove_team_member"](
403
+ member, name, profile=profile, **kwargs
404
+ )
405
+ if result:
406
+ ret["changes"][member] = member_removal
407
+ else:
408
+ ret["result"] = False
409
+ ret["comment"] = f"Failed to remove user {member} from team {name}."
410
+ return ret
411
+
412
+ if member_change: # Refresh team cache
413
+ __salt__["github.list_team_members"](name, profile=profile, ignore_cache=False, **kwargs)
414
+
415
+ if len(test_comments) > 0:
416
+ ret["comment"] = "\n".join(test_comments)
417
+ return ret
418
+
419
+
420
+ def _member_violates_mfa(member, member_info, mfa_deadline, members_without_mfa):
421
+ if member_info.get("mfa_exempt", False):
422
+ return False
423
+ enforce_mfa_from = datetime.datetime.strptime(
424
+ member_info.get("enforce_mfa_from", "1970/01/01"), "%Y/%m/%d"
425
+ ).replace(tzinfo=datetime.UTC)
426
+ return member.lower() in members_without_mfa and (mfa_deadline > enforce_mfa_from)
427
+
428
+
429
+ @_catch_errors
430
+ def team_absent(name, profile="github", **kwargs):
431
+ """
432
+ Ensure a team is absent.
433
+
434
+ .. deprecated:: 1.1.0
435
+ Use :py:func:`github_org.team_absent <saltext.github.states.github_org.team_absent>` instead.
436
+
437
+ Example:
438
+
439
+ .. code-block:: yaml
440
+
441
+ ensure team test is present in github:
442
+ github.team_absent:
443
+ - name: test
444
+
445
+
446
+ name
447
+ This is the name of the team in the organization.
448
+ """
449
+ ret = {"name": name, "changes": {}, "result": None, "comment": ""}
450
+
451
+ target = __salt__["github.get_team"](name, profile=profile, **kwargs)
452
+
453
+ if not target:
454
+ ret["comment"] = f"Team {name} does not exist"
455
+ ret["result"] = True
456
+ return ret
457
+ if __opts__["test"]:
458
+ ret["comment"] = f"Team {name} will be deleted"
459
+ ret["changes"]["old"] = f"Team {name} exists"
460
+ ret["changes"]["new"] = f"Team {name} deleted"
461
+ ret["result"] = None
462
+ return ret
463
+
464
+ result = __salt__["github.remove_team"](name, profile=profile, **kwargs)
465
+
466
+ if result:
467
+ ret["comment"] = f"Deleted team {name}"
468
+ ret["changes"].setdefault("old", f"Team {name} exists")
469
+ ret["changes"].setdefault("new", f"Team {name} deleted")
470
+ ret["result"] = True
471
+ else:
472
+ ret["comment"] = f"Failed to delete {name}"
473
+ ret["result"] = False
474
+ return ret
475
+
476
+
477
+ @_catch_errors
478
+ def repo_present(
479
+ name,
480
+ description=None,
481
+ homepage=None,
482
+ private=None,
483
+ has_issues=None,
484
+ has_wiki=None,
485
+ has_downloads=None,
486
+ auto_init=False,
487
+ gitignore_template=None,
488
+ license_template=None,
489
+ teams=None,
490
+ profile="github",
491
+ **kwargs,
492
+ ):
493
+ """
494
+ Ensure a repository is present
495
+
496
+ .. deprecated:: 1.1.0
497
+ Use :py:func:`github_repo.present <saltext.github.states.github_repo.present>` instead.
498
+
499
+ name
500
+ This is the name of the repository.
501
+
502
+ description
503
+ The description of the repository.
504
+
505
+ homepage
506
+ The URL with more information about the repository.
507
+
508
+ private
509
+ The visiblity of the repository. Note that private repositories require
510
+ a paid GitHub account.
511
+
512
+ has_issues
513
+ Whether to enable issues for this repository.
514
+
515
+ has_wiki
516
+ Whether to enable the wiki for this repository.
517
+
518
+ has_downloads
519
+ Whether to enable downloads for this repository.
520
+
521
+ auto_init
522
+ Whether to create an initial commit with an empty README.
523
+
524
+ gitignore_template
525
+ The desired language or platform for a .gitignore, e.g "Haskell".
526
+
527
+ license_template
528
+ The desired LICENSE template to apply, e.g "mit" or "mozilla".
529
+
530
+ teams
531
+ The teams for which this repo should belong to, specified as a dict of
532
+ team name to permission (``pull``, ``push`` or ``admin``).
533
+
534
+ Example:
535
+
536
+ .. code-block:: yaml
537
+
538
+ Ensure repo my-repo is present in github:
539
+ github.repo_present:
540
+ - name: my-repo
541
+ - description: My very important repository
542
+ """
543
+ ret = {"name": name, "changes": {}, "result": True, "comment": ""}
544
+
545
+ # This is an optimization to cache all repos in the organization up front.
546
+ # The first use of this state will collect all of the repos and save a bunch
547
+ # of API calls for future use.
548
+ __salt__["github.list_repos"](profile=profile)
549
+
550
+ try:
551
+ target = __salt__["github.get_repo_info"](name, profile=profile, **kwargs)
552
+ except CommandExecutionError:
553
+ target = None
554
+
555
+ given_params = {
556
+ "description": description,
557
+ "homepage": homepage,
558
+ "private": private,
559
+ "has_issues": has_issues,
560
+ "has_wiki": has_wiki,
561
+ "has_downloads": has_downloads,
562
+ "auto_init": auto_init,
563
+ "gitignore_template": gitignore_template,
564
+ "license_template": license_template,
565
+ }
566
+
567
+ # Keep track of current_teams if we've fetched them after creating a new repo
568
+ current_teams = None
569
+
570
+ if target: # Repo already exists
571
+ # Some params are only valid on repo creation
572
+ ignore_params = ["auto_init", "gitignore_template", "license_template"]
573
+ parameters = {}
574
+ old_parameters = {}
575
+ for param_name, param_value in given_params.items():
576
+ if (
577
+ param_value is not None
578
+ and param_name not in ignore_params
579
+ and target[param_name] is not param_value
580
+ and target[param_name] != param_value
581
+ ):
582
+ parameters[param_name] = param_value
583
+ old_parameters[param_name] = target[param_name]
584
+
585
+ if len(parameters) > 0:
586
+ repo_change = {
587
+ "old": f"Repo properties were {old_parameters}",
588
+ "new": f"Repo properties (that changed) are {parameters}",
589
+ }
590
+ if __opts__["test"]:
591
+ ret["changes"]["repo"] = repo_change
592
+ ret["result"] = None
593
+ else:
594
+ result = __salt__["github.edit_repo"](name, profile=profile, **parameters)
595
+ if result:
596
+ ret["changes"]["repo"] = repo_change
597
+ else:
598
+ ret["result"] = False
599
+ ret["comment"] = "Failed to update repo properties."
600
+ return ret
601
+
602
+ else: # Repo does not exist - it will be created.
603
+ repo_change = {"old": None, "new": f"Repo {name} has been created"}
604
+ if __opts__["test"]:
605
+ ret["changes"]["repo"] = repo_change
606
+ ret["result"] = None
607
+ else:
608
+ add_params = dict(given_params)
609
+ add_params.update(kwargs)
610
+ result = __salt__["github.add_repo"](name, profile=profile, **add_params)
611
+
612
+ if not result:
613
+ ret["result"] = False
614
+ ret["comment"] = f"Failed to create repo {name}."
615
+ return ret
616
+
617
+ # Turns out that trying to fetch teams for a new repo can 404 immediately
618
+ # after repo creation, so this waits until we can fetch teams successfully
619
+ # before continuing.
620
+ for attempt in range(3):
621
+ time.sleep(1)
622
+ try:
623
+ current_teams = __salt__["github.get_repo_teams"](
624
+ name, profile=profile, **kwargs
625
+ )
626
+ break
627
+ except CommandExecutionError:
628
+ log.info("Attempt %s to fetch new repo %s failed", attempt, name)
629
+
630
+ if current_teams is None:
631
+ ret["result"] = False
632
+ ret["comment"] = f"Failed to verify repo {name} after creation."
633
+ return ret
634
+
635
+ ret["changes"]["repo"] = repo_change
636
+
637
+ if teams is not None:
638
+ if __opts__["test"] and not target:
639
+ # Assume no teams if we're in test mode and the repo doesn't exist
640
+ current_teams = []
641
+ elif current_teams is None:
642
+ current_teams = __salt__["github.get_repo_teams"](name, profile=profile)
643
+ current_team_names = {t["name"] for t in current_teams}
644
+
645
+ # First remove any teams that aren't present
646
+ for team_name in current_team_names:
647
+ if team_name in teams:
648
+ continue
649
+ team_change = {
650
+ "old": f"Repo {name} is in team {team_name}",
651
+ "new": f"Repo {name} is not in team {team_name}",
652
+ }
653
+
654
+ if __opts__["test"]:
655
+ ret["changes"][team_name] = team_change
656
+ ret["result"] = None
657
+ else:
658
+ result = __salt__["github.remove_team_repo"](name, team_name, profile=profile)
659
+ if not result:
660
+ ret["result"] = False
661
+ ret["comment"] = f"Failed to remove repo {name} from team {team_name}."
662
+ return ret
663
+ ret["changes"][team_name] = team_change
664
+
665
+ # Next add or modify any necessary teams
666
+ for team_name, permission in teams.items():
667
+ if team_name not in current_team_names: # Need to add repo to team
668
+ team_change = {
669
+ "old": f"Repo {name} is not in team {team_name}",
670
+ "new": f"Repo {name} is in team {team_name}",
671
+ }
672
+ if __opts__["test"]:
673
+ ret["changes"][team_name] = team_change
674
+ ret["result"] = None
675
+ else:
676
+ result = __salt__["github.add_team_repo"](
677
+ name, team_name, profile=profile, permission=permission
678
+ )
679
+ if not result:
680
+ ret["result"] = False
681
+ ret["comment"] = f"Failed to add repo {name} to team {team_name}."
682
+ return ret
683
+ ret["changes"][team_name] = team_change
684
+ else:
685
+ current_permission = (
686
+ __salt__["github.list_team_repos"](team_name, profile=profile)
687
+ .get(name.lower(), {})
688
+ .get("permission")
689
+ )
690
+ if not current_permission:
691
+ ret["result"] = False
692
+ ret["comment"] = (
693
+ f"Failed to determine current permission for team {team_name} in repo {name}"
694
+ )
695
+ return ret
696
+ if current_permission == permission:
697
+ continue
698
+ team_change = {
699
+ "old": f"Repo {name} in team {team_name} has permission {current_permission}",
700
+ "new": f"Repo {name} in team {team_name} has permission {permission}",
701
+ }
702
+ if __opts__["test"]:
703
+ ret["changes"][team_name] = team_change
704
+ ret["result"] = None
705
+ else:
706
+ result = __salt__["github.add_team_repo"](
707
+ name, team_name, profile=profile, permission=permission
708
+ )
709
+ if not result:
710
+ ret["result"] = False
711
+ ret["comment"] = (
712
+ f"Failed to set permission on repo {name} from team {team_name} to {permission}."
713
+ )
714
+ return ret
715
+ ret["changes"][team_name] = team_change
716
+ return ret
717
+
718
+
719
+ @_catch_errors
720
+ def repo_absent(name, profile="github", **kwargs):
721
+ """
722
+ Ensure a repo is absent.
723
+
724
+ .. deprecated:: 1.1.0
725
+ Use :py:func:`github_repo.absent <saltext.github.states.github_repo.absent>` instead.
726
+
727
+ Example:
728
+
729
+ .. code-block:: yaml
730
+
731
+ ensure repo test is absent in github:
732
+ github.repo_absent:
733
+ - name: test
734
+
735
+ name
736
+ This is the name of the repository in the organization.
737
+ """
738
+ ret = {"name": name, "changes": {}, "result": None, "comment": ""}
739
+
740
+ try:
741
+ target = __salt__["github.get_repo_info"](name, profile=profile, **kwargs)
742
+ except CommandExecutionError:
743
+ target = None
744
+
745
+ if not target:
746
+ ret["comment"] = f"Repo {name} does not exist"
747
+ ret["result"] = True
748
+ return ret
749
+
750
+ if __opts__["test"]:
751
+ ret["comment"] = f"Repo {name} will be deleted"
752
+ ret["changes"]["old"] = f"Repo {name} exists"
753
+ ret["changes"]["new"] = f"Repo {name} deleted"
754
+ ret["result"] = None
755
+ return ret
756
+
757
+ result = __salt__["github.remove_repo"](name, profile=profile, **kwargs)
758
+
759
+ if result:
760
+ ret["comment"] = f"Deleted repo {name}"
761
+ ret["changes"].setdefault("old", f"Repo {name} exists")
762
+ ret["changes"].setdefault("new", f"Repo {name} deleted")
763
+ ret["result"] = True
764
+ return ret
765
+
766
+ ret["comment"] = (
767
+ f"Failed to delete repo {name}. Ensure the delete_repo scope is enabled if using OAuth."
768
+ )
769
+ ret["result"] = False
770
+ return ret
771
+
772
+
773
+ @_catch_errors
774
+ def ruleset_absent(
775
+ name, ruleset_type, profile="github", owner=None, repo_name=None, org_name=None, **kwargs
776
+ ):
777
+ """
778
+ Ensure a ruleset is absent.
779
+
780
+ .. deprecated:: 1.1.0
781
+ Use :py:func:`github_org.ruleset_absent <saltext.github.states.github_org.ruleset_absent>` or
782
+ :py:func:`github_repo.ruleset_absent <saltext.github.states.github_repo.ruleset_absent>` instead.
783
+
784
+ Example:
785
+
786
+ .. code-block:: yaml
787
+
788
+ ensure rulset test is absent in github:
789
+ github.ruleset_absent:
790
+ - name: test
791
+ - ruleset_type: repo
792
+ - owner: test
793
+ - repo_name: test_repo
794
+
795
+ name
796
+ name of the ruleset.
797
+
798
+ ruleset_type
799
+ type of ruleset (``org`` or ``repo``)
800
+ """
801
+
802
+ ret = {"name": name, "changes": {}, "result": None, "comment": ""}
803
+
804
+ if ruleset_type not in ["org", "repo"]:
805
+ ret["result"] = False
806
+ ret["comment"] = "ruleset_type not set to repo or org"
807
+ return ret
808
+
809
+ if ruleset_type == "repo":
810
+ kwargs.update({"owner": owner, "repo_name": repo_name, "ruleset_type": ruleset_type})
811
+ if ruleset_type == "org":
812
+ kwargs.update({"org_name": org_name, "ruleset_type": ruleset_type})
813
+
814
+ rulesets = __salt__["github.list_rulesets"](profile, **kwargs)
815
+
816
+ if rulesets.get("result") is False:
817
+ ret["result"] = False
818
+ ret["comment"] = "error listing rulesets"
819
+ return ret
820
+
821
+ ruleset = next(
822
+ (ruleset for ruleset in (rulesets.get("rulesets") or []) if ruleset["name"] == name), None
823
+ )
824
+ if ruleset is None:
825
+ ret["comment"] = f"Ruleset {name} does not exist"
826
+ ret["result"] = True
827
+ return ret
828
+
829
+ if __opts__["test"]:
830
+ ret["comment"] = f"Ruleset {name} will be deleted"
831
+ ret["changes"]["old"] = f"ruleset {name} exists"
832
+ ret["changes"]["new"] = f"ruleset {name} deleted"
833
+ ret["result"] = None
834
+ return ret
835
+
836
+ kwargs.update({"ruleset_id": ruleset["id"]})
837
+ result = __salt__["github.delete_ruleset"](profile, **kwargs)
838
+
839
+ # delete_ruleset returns a (truthy) dict with result: False on failure
840
+ if result.get("result") is not False:
841
+ ret["comment"] = f"Deleted ruleset {name}"
842
+ ret["changes"]["old"] = f"ruleset {name} exists"
843
+ ret["changes"]["new"] = f"ruleset {name} deleted"
844
+ ret["result"] = True
845
+ return ret
846
+
847
+ ret["comment"] = f"Failed to delete ruleset {name}"
848
+ ret["result"] = False
849
+ return ret
850
+
851
+
852
+ @_catch_errors
853
+ def ruleset_present(
854
+ name,
855
+ ruleset_type,
856
+ profile="github",
857
+ owner=None,
858
+ repo_name=None,
859
+ org_name=None,
860
+ ruleset_params=None,
861
+ **kwargs,
862
+ ):
863
+ """
864
+ Ensure a ruleset is present.
865
+
866
+ .. deprecated:: 1.1.0
867
+ Use :py:func:`github_org.ruleset_present <saltext.github.states.github_org.ruleset_present>` or
868
+ :py:func:`github_repo.ruleset_present <saltext.github.states.github_repo.ruleset_present>` instead.
869
+
870
+ Example:
871
+
872
+ .. code-block:: yaml
873
+
874
+ ensure rulset test is present in github:
875
+ github.ruleset_present:
876
+ - name: test
877
+ - ruleset_type: repo
878
+ - owner: test
879
+ - repo_name: test_repo
880
+ - ruleset_params:
881
+ target: branch
882
+ enforcement: disabled
883
+
884
+ name
885
+ name of the ruleset.
886
+
887
+ ruleset_type
888
+ type of ruleset (``org`` or ``repo``)
889
+
890
+ ruleset_params
891
+ parameters to set the ruleset rules. i.e ``{enforcement: disabled}``
892
+ """
893
+ ret = {"name": name, "changes": {}, "result": True, "comment": ""}
894
+
895
+ if ruleset_type not in ["org", "repo"]:
896
+ ret["result"] = False
897
+ ret["comment"] = "ruleset_type not set to repo or org"
898
+ return ret
899
+
900
+ if ruleset_type == "repo":
901
+ kwargs.update({"owner": owner, "repo_name": repo_name, "ruleset_type": ruleset_type})
902
+ if ruleset_type == "org":
903
+ kwargs.update({"org_name": org_name, "ruleset_type": ruleset_type})
904
+
905
+ if ruleset_params:
906
+ ruleset_params["name"] = name
907
+ else:
908
+ ruleset_params = {}
909
+
910
+ rulesets = __salt__["github.list_rulesets"](profile, **kwargs)
911
+
912
+ if rulesets.get("result") is False:
913
+ ret["result"] = False
914
+ ret["comment"] = "error listing rulesets"
915
+ return ret
916
+
917
+ ruleset = next(
918
+ (ruleset for ruleset in (rulesets.get("rulesets") or []) if ruleset["name"] == name), None
919
+ )
920
+ if ruleset is None:
921
+ ret["changes"] = {"old": "No existing ruleset found", "new": "Ruleset created"}
922
+ if __opts__["test"]:
923
+ ret["result"] = None
924
+ ret["comment"] = "ruleset will be added"
925
+ return ret
926
+ result = __salt__["github.add_ruleset"](profile, ruleset_params=ruleset_params, **kwargs)
927
+ ret["comment"] = "ruleset added"
928
+ if not result.get("id"):
929
+ ret["changes"] = {}
930
+ ret["result"] = False
931
+ ret["comment"] = "ruleset could not be added"
932
+ return ret
933
+
934
+ kwargs.update({"ruleset_id": ruleset["id"]})
935
+ ruleset_info = __salt__["github.get_ruleset"](profile, **kwargs)
936
+ if not ruleset_info.get("id"):
937
+ ret["comment"] = "Could not get ruleset"
938
+ ret["result"] = False
939
+ return ret
940
+
941
+ # Only diff the parameters that are being managed. The server
942
+ # response carries additional server-populated fields (id,
943
+ # timestamps, source, ...) that must not register as drift.
944
+ diff = {key: value for key, value in ruleset_params.items() if ruleset_info.get(key) != value}
945
+ if not diff:
946
+ ret["comment"] = "ruleset present"
947
+ return ret
948
+
949
+ ret["changes"] = {
950
+ "old": {key: ruleset_info.get(key) for key in diff},
951
+ "new": diff,
952
+ }
953
+ if __opts__["test"]:
954
+ ret["result"] = None
955
+ ret["comment"] = "ruleset will be updated"
956
+ return ret
957
+
958
+ kwargs.update({"ruleset_params": ruleset_params})
959
+ result = __salt__["github.update_ruleset"](profile, **kwargs)
960
+ if result.get("id"):
961
+ ret["comment"] = "ruleset updated"
962
+ else:
963
+ ret["changes"] = {}
964
+ ret["result"] = False
965
+ ret["comment"] = "Could not update ruleset"
966
+ return ret