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,128 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Operations on GitHub Pages sites of repositories.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
|
|
7
|
+
from saltext.github.utils import filter_unset
|
|
8
|
+
|
|
9
|
+
log = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def build_source(branch=None, path=None):
|
|
13
|
+
"""
|
|
14
|
+
Build a Pages ``source`` payload from branch and path, or ``None``
|
|
15
|
+
when both are unset.
|
|
16
|
+
|
|
17
|
+
branch
|
|
18
|
+
Repository branch to publish from.
|
|
19
|
+
|
|
20
|
+
path
|
|
21
|
+
Repository directory to publish from, either ``/`` or ``/docs``.
|
|
22
|
+
"""
|
|
23
|
+
source = filter_unset({"branch": branch, "path": path})
|
|
24
|
+
return source or None
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def get(conn, repo_name, *, owner=None):
|
|
28
|
+
"""
|
|
29
|
+
Return information about the GitHub Pages site of a repository.
|
|
30
|
+
|
|
31
|
+
conn
|
|
32
|
+
:class:`~saltext.github.utils.gh.client.GitHubConnection` to use.
|
|
33
|
+
|
|
34
|
+
repo_name
|
|
35
|
+
Repository name.
|
|
36
|
+
|
|
37
|
+
owner
|
|
38
|
+
Owner of the repository. Defaults to the connection's owner.
|
|
39
|
+
|
|
40
|
+
Raises :class:`~saltext.github.utils.gh.exceptions.GitHubNotFoundError`
|
|
41
|
+
if the repository has no Pages site.
|
|
42
|
+
"""
|
|
43
|
+
return conn.repo_query(owner, repo_name, "pages")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def create(conn, repo_name, *, build_type=None, source=None, owner=None):
|
|
47
|
+
"""
|
|
48
|
+
Configure a GitHub Pages site for a repository.
|
|
49
|
+
Returns the data of the created site.
|
|
50
|
+
|
|
51
|
+
conn
|
|
52
|
+
:class:`~saltext.github.utils.gh.client.GitHubConnection` to use.
|
|
53
|
+
|
|
54
|
+
repo_name
|
|
55
|
+
Repository name.
|
|
56
|
+
|
|
57
|
+
build_type
|
|
58
|
+
Process by which the site is built, either ``legacy``
|
|
59
|
+
(deploy from a branch) or ``workflow`` (GitHub Actions).
|
|
60
|
+
|
|
61
|
+
source
|
|
62
|
+
For ``legacy`` builds, the deployment source as a mapping with
|
|
63
|
+
``branch`` and ``path``.
|
|
64
|
+
|
|
65
|
+
owner
|
|
66
|
+
Owner of the repository. Defaults to the connection's owner.
|
|
67
|
+
"""
|
|
68
|
+
return conn.repo_query(
|
|
69
|
+
owner,
|
|
70
|
+
repo_name,
|
|
71
|
+
"pages",
|
|
72
|
+
method="POST",
|
|
73
|
+
data=filter_unset({"build_type": build_type, "source": source}),
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def update(
|
|
78
|
+
conn, repo_name, *, build_type=None, source=None, cname=..., https_enforced=None, owner=None
|
|
79
|
+
):
|
|
80
|
+
"""
|
|
81
|
+
Update the GitHub Pages site configuration of a repository.
|
|
82
|
+
|
|
83
|
+
conn
|
|
84
|
+
:class:`~saltext.github.utils.gh.client.GitHubConnection` to use.
|
|
85
|
+
|
|
86
|
+
repo_name
|
|
87
|
+
Repository name.
|
|
88
|
+
|
|
89
|
+
build_type
|
|
90
|
+
Process by which the site is built, either ``legacy``
|
|
91
|
+
(deploy from a branch) or ``workflow`` (GitHub Actions).
|
|
92
|
+
|
|
93
|
+
source
|
|
94
|
+
For ``legacy`` builds, the deployment source as a mapping with
|
|
95
|
+
``branch`` and ``path``.
|
|
96
|
+
|
|
97
|
+
cname
|
|
98
|
+
Custom domain for the site. Pass ``None`` explicitly
|
|
99
|
+
to remove it.
|
|
100
|
+
|
|
101
|
+
https_enforced
|
|
102
|
+
Enforce HTTPS for the site.
|
|
103
|
+
|
|
104
|
+
owner
|
|
105
|
+
Owner of the repository. Defaults to the connection's owner.
|
|
106
|
+
"""
|
|
107
|
+
data = filter_unset(
|
|
108
|
+
{"build_type": build_type, "source": source, "https_enforced": https_enforced}
|
|
109
|
+
)
|
|
110
|
+
if cname is not ...:
|
|
111
|
+
data["cname"] = cname
|
|
112
|
+
conn.repo_query(owner, repo_name, "pages", method="PUT", data=data)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def delete(conn, repo_name, *, owner=None):
|
|
116
|
+
"""
|
|
117
|
+
Delete the GitHub Pages site of a repository.
|
|
118
|
+
|
|
119
|
+
conn
|
|
120
|
+
:class:`~saltext.github.utils.gh.client.GitHubConnection` to use.
|
|
121
|
+
|
|
122
|
+
repo_name
|
|
123
|
+
Repository name.
|
|
124
|
+
|
|
125
|
+
owner
|
|
126
|
+
Owner of the repository. Defaults to the connection's owner.
|
|
127
|
+
"""
|
|
128
|
+
conn.repo_query(owner, repo_name, "pages", method="DELETE")
|
|
@@ -0,0 +1,458 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Resolution of Salt profile configuration into GitHub connections,
|
|
3
|
+
shared between the ``github``, ``github_org`` and ``github_repo``
|
|
4
|
+
execution modules as well as the ``github`` resource type.
|
|
5
|
+
|
|
6
|
+
Profiles are resolved directly from ``opts``, mirroring ``config.get``'s
|
|
7
|
+
lookup order: minion configuration, then grains, then pillar, then
|
|
8
|
+
master configuration (which reaches the minion via the master's
|
|
9
|
+
``pillar_opts`` setting, rendered under ``pillar["master"]``). SDB URIs
|
|
10
|
+
in profile values are rendered on access, like ``config.get`` renders
|
|
11
|
+
them. ``config.get`` itself cannot serve as the backend since it is
|
|
12
|
+
unavailable to parts of the resource subsystem (type discovery runs
|
|
13
|
+
before the minion's module loader exists).
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import logging
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
from salt.exceptions import CommandExecutionError
|
|
20
|
+
|
|
21
|
+
from saltext.github.utils.gh.cache import MISS
|
|
22
|
+
from saltext.github.utils.gh.cache import PersistentCache
|
|
23
|
+
from saltext.github.utils.gh.client import API_URL_DEFAULT
|
|
24
|
+
from saltext.github.utils.gh.client import GitHubConnection
|
|
25
|
+
from saltext.github.utils.gh.client import build_app_auth
|
|
26
|
+
from saltext.github.utils.gh.client import get_app_connection_ident
|
|
27
|
+
from saltext.github.utils.gh.client import get_connection_ident
|
|
28
|
+
from saltext.github.utils.gh.client import resolve_app_installation_id
|
|
29
|
+
|
|
30
|
+
log = logging.getLogger(__name__)
|
|
31
|
+
|
|
32
|
+
_UNSET = object()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _render_value(value, opts, utils=None):
|
|
36
|
+
"""
|
|
37
|
+
Render ``sdb://`` URIs in scalar configuration values, mirroring
|
|
38
|
+
what ``config.get`` does for profile values.
|
|
39
|
+
"""
|
|
40
|
+
if isinstance(value, str) and value.startswith("sdb://"):
|
|
41
|
+
# Only required to resolve SDB URIs
|
|
42
|
+
import salt.utils.sdb # pylint: disable=import-outside-toplevel
|
|
43
|
+
|
|
44
|
+
return salt.utils.sdb.sdb_get(value, opts, utils=utils)
|
|
45
|
+
return value
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def get_config_value(profile, config_name, *, opts, context=None, utils=None, default=_UNSET):
|
|
49
|
+
"""
|
|
50
|
+
Return a profile's configuration value based on the supplied
|
|
51
|
+
configuration name. See the module docstring for the lookup order
|
|
52
|
+
and SDB handling; the first scope carrying the requested value wins.
|
|
53
|
+
|
|
54
|
+
profile
|
|
55
|
+
Profile name that contains configuration information.
|
|
56
|
+
|
|
57
|
+
config_name
|
|
58
|
+
Configuration item name to look up.
|
|
59
|
+
|
|
60
|
+
opts
|
|
61
|
+
The minion configuration (``__opts__``).
|
|
62
|
+
|
|
63
|
+
context
|
|
64
|
+
Pass ``__context__`` to cache rendered values, saving repeated
|
|
65
|
+
SDB lookups.
|
|
66
|
+
|
|
67
|
+
utils
|
|
68
|
+
Pass ``__utils__`` when available to avoid building a fresh
|
|
69
|
+
utils loader for SDB rendering.
|
|
70
|
+
|
|
71
|
+
default
|
|
72
|
+
Value to return when the profile does not carry the item.
|
|
73
|
+
Without it, a missing item raises a ``CommandExecutionError``.
|
|
74
|
+
"""
|
|
75
|
+
cache = None
|
|
76
|
+
if context is not None:
|
|
77
|
+
cache = context.setdefault("gh_profile", {}).setdefault(profile, {})
|
|
78
|
+
if cache is None or config_name not in cache:
|
|
79
|
+
pillar = opts.get("pillar") or {}
|
|
80
|
+
master_pillar = pillar.get("master")
|
|
81
|
+
scopes = (
|
|
82
|
+
opts,
|
|
83
|
+
opts.get("grains") or {},
|
|
84
|
+
pillar,
|
|
85
|
+
master_pillar if isinstance(master_pillar, dict) else {},
|
|
86
|
+
)
|
|
87
|
+
configs = [
|
|
88
|
+
scope[profile]
|
|
89
|
+
for scope in scopes
|
|
90
|
+
if isinstance(scope.get(profile), dict) and scope[profile]
|
|
91
|
+
]
|
|
92
|
+
if not configs:
|
|
93
|
+
raise CommandExecutionError(
|
|
94
|
+
f"Authentication information could not be found for the '{profile}' profile."
|
|
95
|
+
)
|
|
96
|
+
value = None
|
|
97
|
+
for config in configs:
|
|
98
|
+
found = config.get(config_name)
|
|
99
|
+
if found is not None:
|
|
100
|
+
value = _render_value(found, opts, utils=utils)
|
|
101
|
+
break
|
|
102
|
+
if cache is not None:
|
|
103
|
+
cache[config_name] = value
|
|
104
|
+
else:
|
|
105
|
+
value = cache[config_name]
|
|
106
|
+
|
|
107
|
+
if value is None:
|
|
108
|
+
if default is not _UNSET:
|
|
109
|
+
return default
|
|
110
|
+
raise CommandExecutionError(
|
|
111
|
+
f"The '{config_name}' parameter was not found in the '{profile}' profile."
|
|
112
|
+
)
|
|
113
|
+
return value
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def get_api_url(profile, *, opts, context=None, utils=None):
|
|
117
|
+
"""
|
|
118
|
+
Return the configured GitHub API base URL, defaulting to the public one.
|
|
119
|
+
|
|
120
|
+
profile
|
|
121
|
+
Profile name that contains configuration information.
|
|
122
|
+
|
|
123
|
+
opts
|
|
124
|
+
The minion configuration (``__opts__``).
|
|
125
|
+
|
|
126
|
+
context
|
|
127
|
+
Pass ``__context__`` to cache rendered values.
|
|
128
|
+
|
|
129
|
+
utils
|
|
130
|
+
Pass ``__utils__`` when available (SDB rendering).
|
|
131
|
+
"""
|
|
132
|
+
return get_config_value(
|
|
133
|
+
profile, "api_url", opts=opts, context=context, utils=utils, default=API_URL_DEFAULT
|
|
134
|
+
).rstrip("/")
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def get_owner(profile, *, opts, context=None, utils=None):
|
|
138
|
+
"""
|
|
139
|
+
Return the configured default repository owner: the profile's
|
|
140
|
+
``owner``, falling back to the legacy ``org_name`` alias. Returns
|
|
141
|
+
``None`` when neither is set, making the connection default to the
|
|
142
|
+
token's user.
|
|
143
|
+
|
|
144
|
+
profile
|
|
145
|
+
Profile name that contains configuration information.
|
|
146
|
+
|
|
147
|
+
opts
|
|
148
|
+
The minion configuration (``__opts__``).
|
|
149
|
+
|
|
150
|
+
context
|
|
151
|
+
Pass ``__context__`` to cache rendered values.
|
|
152
|
+
|
|
153
|
+
utils
|
|
154
|
+
Pass ``__utils__`` when available (SDB rendering).
|
|
155
|
+
"""
|
|
156
|
+
for key in ("owner", "org_name"):
|
|
157
|
+
owner = get_config_value(
|
|
158
|
+
profile, key, opts=opts, context=context, utils=utils, default=None
|
|
159
|
+
)
|
|
160
|
+
if owner is not None:
|
|
161
|
+
return owner
|
|
162
|
+
return None
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def get_conn(profile, *, opts, context, utils=None):
|
|
166
|
+
"""
|
|
167
|
+
Return a :class:`~saltext.github.utils.gh.client.GitHubConnection`
|
|
168
|
+
for a profile, cached in ``context`` for performance.
|
|
169
|
+
|
|
170
|
+
Unless the profile disables it (:ghconf:`cache`), the connection
|
|
171
|
+
carries a :class:`~saltext.github.utils.gh.cache.PersistentCache`
|
|
172
|
+
for slow-changing API data, surviving across processes.
|
|
173
|
+
|
|
174
|
+
profile
|
|
175
|
+
Profile name that contains configuration information.
|
|
176
|
+
|
|
177
|
+
opts
|
|
178
|
+
The minion configuration (``__opts__``).
|
|
179
|
+
|
|
180
|
+
context
|
|
181
|
+
Pass ``__context__``. Caches the connection and rendered
|
|
182
|
+
profile values.
|
|
183
|
+
|
|
184
|
+
utils
|
|
185
|
+
Pass ``__utils__`` when available (SDB rendering).
|
|
186
|
+
"""
|
|
187
|
+
profile_key = f"github.profile.{profile}"
|
|
188
|
+
if profile_key in context:
|
|
189
|
+
return context[profile_key]
|
|
190
|
+
|
|
191
|
+
token = get_config_value(
|
|
192
|
+
profile, "token", opts=opts, context=context, utils=utils, default=None
|
|
193
|
+
)
|
|
194
|
+
app_id = get_config_value(
|
|
195
|
+
profile, "app_id", opts=opts, context=context, utils=utils, default=None
|
|
196
|
+
)
|
|
197
|
+
api_url = get_api_url(profile, opts=opts, context=context, utils=utils)
|
|
198
|
+
owner = get_owner(profile, opts=opts, context=context, utils=utils)
|
|
199
|
+
permissions = get_config_value(
|
|
200
|
+
profile, "permissions", opts=opts, context=context, utils=utils, default=None
|
|
201
|
+
)
|
|
202
|
+
if token is not None and app_id is not None:
|
|
203
|
+
raise CommandExecutionError(
|
|
204
|
+
f"The '{profile}' profile specifies both 'token' and 'app_id', "
|
|
205
|
+
"which are mutually exclusive."
|
|
206
|
+
)
|
|
207
|
+
if token is not None:
|
|
208
|
+
if permissions is not None:
|
|
209
|
+
raise CommandExecutionError(
|
|
210
|
+
f"The 'permissions' parameter is only valid for GitHub App ('app_id') "
|
|
211
|
+
f"profiles, but the '{profile}' profile uses token authentication."
|
|
212
|
+
)
|
|
213
|
+
# Hash the token to keep the secret out of context dumps
|
|
214
|
+
ident = get_connection_ident(token, api_url)
|
|
215
|
+
elif app_id is not None:
|
|
216
|
+
if not owner:
|
|
217
|
+
raise CommandExecutionError(
|
|
218
|
+
f"GitHub App authentication requires the 'owner' parameter "
|
|
219
|
+
f"in the '{profile}' profile."
|
|
220
|
+
)
|
|
221
|
+
if permissions is not None and not isinstance(permissions, dict):
|
|
222
|
+
raise CommandExecutionError(
|
|
223
|
+
f"The 'permissions' parameter of the '{profile}' profile must be a "
|
|
224
|
+
"mapping of permission name to access level."
|
|
225
|
+
)
|
|
226
|
+
# Derived from the app's identity, not its short-lived
|
|
227
|
+
# installation tokens - persistent cache banks survive rotation.
|
|
228
|
+
# Differently restricted profiles must not share connections.
|
|
229
|
+
ident = get_app_connection_ident(app_id, owner, api_url, permissions)
|
|
230
|
+
else:
|
|
231
|
+
raise CommandExecutionError(
|
|
232
|
+
f"The '{profile}' profile is missing authentication credentials. "
|
|
233
|
+
"Specify either 'token' or 'app_id' + 'private_key'/'private_key_path'."
|
|
234
|
+
)
|
|
235
|
+
conn_key = f"github.conn.{ident}"
|
|
236
|
+
|
|
237
|
+
# This config is HTTP connection-independent. We should reuse a client, if it exists.
|
|
238
|
+
cache_enabled = get_config_value(
|
|
239
|
+
profile, "cache", opts=opts, context=context, utils=utils, default=True
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
if conn_key in context:
|
|
243
|
+
other_conn = context[conn_key]
|
|
244
|
+
store = ...
|
|
245
|
+
if bool(other_conn.store) is not cache_enabled:
|
|
246
|
+
store = PersistentCache(opts, ident) if cache_enabled else None
|
|
247
|
+
context[profile_key] = context[conn_key].with_(owner=owner, store=store)
|
|
248
|
+
else:
|
|
249
|
+
store = PersistentCache(opts, ident) if cache_enabled else None
|
|
250
|
+
auth = token
|
|
251
|
+
if auth is None:
|
|
252
|
+
auth = _get_app_auth(
|
|
253
|
+
profile,
|
|
254
|
+
app_id,
|
|
255
|
+
owner=owner,
|
|
256
|
+
api_url=api_url,
|
|
257
|
+
permissions=permissions,
|
|
258
|
+
store=store,
|
|
259
|
+
opts=opts,
|
|
260
|
+
context=context,
|
|
261
|
+
utils=utils,
|
|
262
|
+
)
|
|
263
|
+
context[profile_key] = context[conn_key] = GitHubConnection(
|
|
264
|
+
auth,
|
|
265
|
+
owner,
|
|
266
|
+
api_url=api_url,
|
|
267
|
+
ident=ident,
|
|
268
|
+
store=store,
|
|
269
|
+
)
|
|
270
|
+
return context[profile_key]
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def _get_app_auth(
|
|
274
|
+
profile, app_id, *, owner, api_url, permissions, store, opts, context, utils=None
|
|
275
|
+
):
|
|
276
|
+
"""
|
|
277
|
+
Build the PyGithub auth object for a GitHub App profile. When the
|
|
278
|
+
profile does not pin :ghconf:`installation_id`, the app's
|
|
279
|
+
installation on ``owner`` is discovered via the API (cached in
|
|
280
|
+
``store``, when available).
|
|
281
|
+
"""
|
|
282
|
+
private_key = get_config_value(
|
|
283
|
+
profile, "private_key", opts=opts, context=context, utils=utils, default=None
|
|
284
|
+
)
|
|
285
|
+
private_key_path = get_config_value(
|
|
286
|
+
profile, "private_key_path", opts=opts, context=context, utils=utils, default=None
|
|
287
|
+
)
|
|
288
|
+
if (private_key is None) is (private_key_path is None):
|
|
289
|
+
raise CommandExecutionError(
|
|
290
|
+
f"GitHub App authentication requires exactly one of 'private_key' or "
|
|
291
|
+
f"'private_key_path', the '{profile}' profile specifies "
|
|
292
|
+
f"{'both' if private_key is not None else 'neither'}."
|
|
293
|
+
)
|
|
294
|
+
if private_key_path is not None:
|
|
295
|
+
try:
|
|
296
|
+
private_key = Path(private_key_path).read_text(encoding="utf-8")
|
|
297
|
+
except OSError as err:
|
|
298
|
+
raise CommandExecutionError(
|
|
299
|
+
f"Failed reading 'private_key_path' of the '{profile}' profile: {err}"
|
|
300
|
+
) from err
|
|
301
|
+
installation_id = get_config_value(
|
|
302
|
+
profile, "installation_id", opts=opts, context=context, utils=utils, default=None
|
|
303
|
+
)
|
|
304
|
+
if installation_id is None and store is not None:
|
|
305
|
+
installation_id = store.get_meta("installation_id")
|
|
306
|
+
if installation_id is MISS:
|
|
307
|
+
installation_id = None
|
|
308
|
+
if installation_id is None:
|
|
309
|
+
installation_id = resolve_app_installation_id(app_id, private_key, owner, api_url)
|
|
310
|
+
if store is not None:
|
|
311
|
+
store.set_meta("installation_id", installation_id)
|
|
312
|
+
return build_app_auth(app_id, private_key, installation_id, permissions)
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def resource_decl(opts):
|
|
316
|
+
"""
|
|
317
|
+
Return the ``github`` resource type's declaration from the resource
|
|
318
|
+
pillar subtree (``{}`` when unset or not a mapping).
|
|
319
|
+
|
|
320
|
+
opts
|
|
321
|
+
The minion configuration (``__opts__``).
|
|
322
|
+
"""
|
|
323
|
+
# Local import: the resource subsystem only exists in Salt >= 3008,
|
|
324
|
+
# while this module is also imported on earlier releases.
|
|
325
|
+
import salt.utils.resources # pylint: disable=import-outside-toplevel
|
|
326
|
+
|
|
327
|
+
decl = salt.utils.resources.pillar_resources_tree(opts).get("github")
|
|
328
|
+
return decl if isinstance(decl, dict) else {}
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
def resource_profile(opts):
|
|
332
|
+
"""
|
|
333
|
+
Return the default profile name configured in the ``github``
|
|
334
|
+
resource declaration, defaulting to ``github``.
|
|
335
|
+
|
|
336
|
+
opts
|
|
337
|
+
The minion configuration (``__opts__``).
|
|
338
|
+
"""
|
|
339
|
+
return resource_decl(opts).get("profile") or "github"
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def resource_owners(opts):
|
|
343
|
+
"""
|
|
344
|
+
Return the owners named in the ``github`` resource declaration as a
|
|
345
|
+
list (an empty one when unset). The declaration's ``owners`` may be
|
|
346
|
+
a plain list of owner names or a mapping of owner name to per-owner
|
|
347
|
+
settings (``profile``, ``repos``, ``exclude``, ``include_archived``).
|
|
348
|
+
|
|
349
|
+
opts
|
|
350
|
+
The minion configuration (``__opts__``).
|
|
351
|
+
"""
|
|
352
|
+
owners = resource_decl(opts).get("owners")
|
|
353
|
+
if isinstance(owners, dict):
|
|
354
|
+
return list(owners)
|
|
355
|
+
return list(owners or [])
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def _owner_settings(opts, owner):
|
|
359
|
+
"""
|
|
360
|
+
Return one owner's settings mapping from the declaration's
|
|
361
|
+
``owners``, matched case-insensitively (``{}`` when the owners are
|
|
362
|
+
a plain list or the owner carries no settings).
|
|
363
|
+
"""
|
|
364
|
+
owners = resource_decl(opts).get("owners")
|
|
365
|
+
if isinstance(owners, dict) and owner is not None:
|
|
366
|
+
for name, settings in owners.items():
|
|
367
|
+
if name.lower() == owner.lower() and isinstance(settings, dict):
|
|
368
|
+
return settings
|
|
369
|
+
return {}
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
def resource_owner_setting(opts, owner, key, default=None):
|
|
373
|
+
"""
|
|
374
|
+
Return a declaration setting for one owner: the owner's own value
|
|
375
|
+
from the ``owners`` mapping when set, otherwise the
|
|
376
|
+
declaration-level value, otherwise ``default``.
|
|
377
|
+
|
|
378
|
+
opts
|
|
379
|
+
The minion configuration (``__opts__``).
|
|
380
|
+
|
|
381
|
+
owner
|
|
382
|
+
Owner (organization or user) login.
|
|
383
|
+
|
|
384
|
+
key
|
|
385
|
+
Setting name (``profile``, ``repos``, ``exclude``,
|
|
386
|
+
``include_archived``).
|
|
387
|
+
|
|
388
|
+
default
|
|
389
|
+
Value to return when neither the owner nor the declaration
|
|
390
|
+
carries the setting.
|
|
391
|
+
"""
|
|
392
|
+
value = _owner_settings(opts, owner).get(key)
|
|
393
|
+
if value is None:
|
|
394
|
+
value = resource_decl(opts).get(key)
|
|
395
|
+
return value if value is not None else default
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
def resource_owner_profile(opts, owner):
|
|
399
|
+
"""
|
|
400
|
+
Return the profile name to use for one owner's repositories: the
|
|
401
|
+
owner's ``profile`` override from the declaration's ``owners``
|
|
402
|
+
mapping when present, otherwise the declaration's default profile.
|
|
403
|
+
Owner names are matched case-insensitively.
|
|
404
|
+
|
|
405
|
+
opts
|
|
406
|
+
The minion configuration (``__opts__``).
|
|
407
|
+
|
|
408
|
+
owner
|
|
409
|
+
Owner (organization or user) login.
|
|
410
|
+
"""
|
|
411
|
+
return resource_owner_setting(opts, owner, "profile", "github")
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
def resource_profiles(opts):
|
|
415
|
+
"""
|
|
416
|
+
Return all distinct profile names referenced by the ``github``
|
|
417
|
+
resource declaration: the default one plus any per-owner overrides.
|
|
418
|
+
|
|
419
|
+
opts
|
|
420
|
+
The minion configuration (``__opts__``).
|
|
421
|
+
"""
|
|
422
|
+
profiles = [resource_profile(opts)]
|
|
423
|
+
owners = resource_decl(opts).get("owners")
|
|
424
|
+
if isinstance(owners, dict):
|
|
425
|
+
for settings in owners.values():
|
|
426
|
+
if isinstance(settings, dict) and settings.get("profile"):
|
|
427
|
+
if settings["profile"] not in profiles:
|
|
428
|
+
profiles.append(settings["profile"])
|
|
429
|
+
return profiles
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
def resource_conn(opts, context, *, profile=None, owner=None, utils=None):
|
|
433
|
+
"""
|
|
434
|
+
Return the :class:`~saltext.github.utils.gh.client.GitHubConnection`
|
|
435
|
+
for a ``github`` resource context, resolved from ``opts``.
|
|
436
|
+
|
|
437
|
+
opts
|
|
438
|
+
The minion configuration (``__opts__``).
|
|
439
|
+
|
|
440
|
+
context
|
|
441
|
+
Cache mapping for the connection (``__context__``).
|
|
442
|
+
|
|
443
|
+
profile
|
|
444
|
+
Profile name override. When unset, the profile is resolved from
|
|
445
|
+
the resource declaration: the targeted ``owner``'s override when
|
|
446
|
+
declared, otherwise the declaration's default profile
|
|
447
|
+
(``github`` when unset).
|
|
448
|
+
|
|
449
|
+
owner
|
|
450
|
+
Owner the connection is for, used to resolve per-owner profile
|
|
451
|
+
overrides. Ignored when ``profile`` is passed.
|
|
452
|
+
|
|
453
|
+
utils
|
|
454
|
+
Pass ``__utils__`` when available (SDB rendering).
|
|
455
|
+
"""
|
|
456
|
+
return get_conn(
|
|
457
|
+
profile or resource_owner_profile(opts, owner), opts=opts, context=context, utils=utils
|
|
458
|
+
)
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Operations on GitHub repository pull requests.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
|
|
7
|
+
log = logging.getLogger(__name__)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def list_all(
|
|
11
|
+
conn,
|
|
12
|
+
repo_name,
|
|
13
|
+
*,
|
|
14
|
+
state="open",
|
|
15
|
+
head=None,
|
|
16
|
+
base=None,
|
|
17
|
+
sort="created",
|
|
18
|
+
direction="desc",
|
|
19
|
+
per_page=None,
|
|
20
|
+
owner=None,
|
|
21
|
+
):
|
|
22
|
+
"""
|
|
23
|
+
Return the raw data of all pull requests in a repository matching the
|
|
24
|
+
search options.
|
|
25
|
+
|
|
26
|
+
conn
|
|
27
|
+
:class:`~saltext.github.utils.gh.client.GitHubConnection` to use.
|
|
28
|
+
|
|
29
|
+
repo_name
|
|
30
|
+
Repository whose pull requests to list.
|
|
31
|
+
|
|
32
|
+
state
|
|
33
|
+
Filter returned pull requests by their state. Can be either
|
|
34
|
+
``open``, ``closed``, or ``all``. Default is ``open``.
|
|
35
|
+
|
|
36
|
+
head
|
|
37
|
+
Filter pull requests by head user and branch name in the format
|
|
38
|
+
``user:ref-name``.
|
|
39
|
+
|
|
40
|
+
base
|
|
41
|
+
Filter pulls by base branch name, e.g. ``gh-pages``.
|
|
42
|
+
|
|
43
|
+
sort
|
|
44
|
+
Field to sort results by. Either ``created``, ``updated``,
|
|
45
|
+
``popularity`` (comment count), or ``long-running`` (age,
|
|
46
|
+
filtering by pull requests updated within the last month).
|
|
47
|
+
Default is ``created``.
|
|
48
|
+
|
|
49
|
+
direction
|
|
50
|
+
Sort direction. Either ``asc`` or ``desc``. Default is ``desc``.
|
|
51
|
+
|
|
52
|
+
per_page
|
|
53
|
+
Pagination size, maximum 100.
|
|
54
|
+
|
|
55
|
+
owner
|
|
56
|
+
Owner of the repository. Defaults to the connection's owner.
|
|
57
|
+
"""
|
|
58
|
+
params = {}
|
|
59
|
+
|
|
60
|
+
# Build API arguments, as necessary.
|
|
61
|
+
if head:
|
|
62
|
+
params["head"] = head
|
|
63
|
+
if base:
|
|
64
|
+
params["base"] = base
|
|
65
|
+
if per_page:
|
|
66
|
+
params["per_page"] = per_page
|
|
67
|
+
|
|
68
|
+
# Only pass the following API args if they're not the defaults listed.
|
|
69
|
+
if state and state != "open":
|
|
70
|
+
params["state"] = state
|
|
71
|
+
if sort and sort != "created":
|
|
72
|
+
params["sort"] = sort
|
|
73
|
+
if direction and direction != "desc":
|
|
74
|
+
params["direction"] = direction
|
|
75
|
+
|
|
76
|
+
return conn.repo_query(owner, repo_name, "pulls", params=params)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def format_pr(pr_):
|
|
80
|
+
r"""
|
|
81
|
+
Format API return information into a more manageable
|
|
82
|
+
and useful dictionary for pull request information.
|
|
83
|
+
|
|
84
|
+
Legacy function, only used by deprecated callers.
|
|
85
|
+
|
|
86
|
+
pr\_
|
|
87
|
+
Pull request to format.
|
|
88
|
+
"""
|
|
89
|
+
ret = {
|
|
90
|
+
"id": pr_.get("id"),
|
|
91
|
+
"pr_number": pr_.get("number"),
|
|
92
|
+
"state": pr_.get("state"),
|
|
93
|
+
"title": pr_.get("title"),
|
|
94
|
+
"user": pr_.get("user").get("login"),
|
|
95
|
+
"html_url": pr_.get("html_url"),
|
|
96
|
+
"base_branch": pr_.get("base").get("ref"),
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return ret
|