salt-api-cli 1.0.0__tar.gz → 1.1.0__tar.gz
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.
- {salt_api_cli-1.0.0/salt_api_cli.egg-info → salt_api_cli-1.1.0}/PKG-INFO +13 -12
- {salt_api_cli-1.0.0 → salt_api_cli-1.1.0}/README.md +11 -11
- {salt_api_cli-1.0.0 → salt_api_cli-1.1.0}/pyproject.toml +4 -2
- {salt_api_cli-1.0.0 → salt_api_cli-1.1.0}/salt_api_cli/cli.py +204 -45
- salt_api_cli-1.1.0/salt_api_cli/version.py +1 -0
- {salt_api_cli-1.0.0 → salt_api_cli-1.1.0/salt_api_cli.egg-info}/PKG-INFO +13 -12
- salt_api_cli-1.1.0/salt_api_cli.egg-info/entry_points.txt +2 -0
- {salt_api_cli-1.0.0 → salt_api_cli-1.1.0}/salt_api_cli.egg-info/requires.txt +1 -0
- salt_api_cli-1.0.0/salt_api_cli/version.py +0 -1
- salt_api_cli-1.0.0/salt_api_cli.egg-info/entry_points.txt +0 -2
- {salt_api_cli-1.0.0 → salt_api_cli-1.1.0}/MANIFEST.in +0 -0
- {salt_api_cli-1.0.0 → salt_api_cli-1.1.0}/salt_api_cli/__init__.py +0 -0
- {salt_api_cli-1.0.0 → salt_api_cli-1.1.0}/salt_api_cli/__main__.py +0 -0
- {salt_api_cli-1.0.0 → salt_api_cli-1.1.0}/salt_api_cli/py.typed +0 -0
- {salt_api_cli-1.0.0 → salt_api_cli-1.1.0}/salt_api_cli.egg-info/SOURCES.txt +0 -0
- {salt_api_cli-1.0.0 → salt_api_cli-1.1.0}/salt_api_cli.egg-info/dependency_links.txt +0 -0
- {salt_api_cli-1.0.0 → salt_api_cli-1.1.0}/salt_api_cli.egg-info/top_level.txt +0 -0
- {salt_api_cli-1.0.0 → salt_api_cli-1.1.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: salt-api-cli
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.1.0
|
|
4
4
|
Summary: CLI to access salt-api
|
|
5
5
|
Author-email: Pradish Bijukchhe <pradish@sandbox.com.np>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -9,6 +9,7 @@ Project-URL: Issues, https://github.com/sandbox-pokhara/saltapi-cli/issues
|
|
|
9
9
|
Classifier: Programming Language :: Python :: 3
|
|
10
10
|
Requires-Python: >=3.11
|
|
11
11
|
Description-Content-Type: text/markdown
|
|
12
|
+
Requires-Dist: typeguard>=4.5.2
|
|
12
13
|
Provides-Extra: pre-commit
|
|
13
14
|
Requires-Dist: pre-commit; extra == "pre-commit"
|
|
14
15
|
|
|
@@ -52,23 +53,23 @@ certificate verification.
|
|
|
52
53
|
|
|
53
54
|
```
|
|
54
55
|
# Local client — fan out to minions
|
|
55
|
-
salt
|
|
56
|
-
salt
|
|
57
|
-
salt
|
|
56
|
+
salt local '*' test.ping
|
|
57
|
+
salt local 'bml*' cmd.run 'whoami'
|
|
58
|
+
salt local 'bml1' cmd.run 'Get-Date' shell=powershell
|
|
58
59
|
|
|
59
60
|
# Runner client (master-side: manage.status, jobs.list_jobs, ...)
|
|
60
|
-
salt
|
|
61
|
-
salt
|
|
61
|
+
salt runner manage.status
|
|
62
|
+
salt runner jobs.list_jobs
|
|
62
63
|
|
|
63
64
|
# Wheel client (master-side, low-level)
|
|
64
|
-
salt
|
|
65
|
+
salt wheel key.list_all
|
|
65
66
|
|
|
66
67
|
# Key management (high-level wrapper around the wheel client)
|
|
67
|
-
salt
|
|
68
|
-
salt
|
|
69
|
-
salt
|
|
70
|
-
salt
|
|
71
|
-
salt
|
|
68
|
+
salt keys list
|
|
69
|
+
salt keys accept <id-or-glob>
|
|
70
|
+
salt keys accept-all
|
|
71
|
+
salt keys reject <id-or-glob>
|
|
72
|
+
salt keys delete <id-or-glob>
|
|
72
73
|
```
|
|
73
74
|
|
|
74
75
|
Any `key=value` argument is parsed as a kwarg to the salt function;
|
|
@@ -38,23 +38,23 @@ certificate verification.
|
|
|
38
38
|
|
|
39
39
|
```
|
|
40
40
|
# Local client — fan out to minions
|
|
41
|
-
salt
|
|
42
|
-
salt
|
|
43
|
-
salt
|
|
41
|
+
salt local '*' test.ping
|
|
42
|
+
salt local 'bml*' cmd.run 'whoami'
|
|
43
|
+
salt local 'bml1' cmd.run 'Get-Date' shell=powershell
|
|
44
44
|
|
|
45
45
|
# Runner client (master-side: manage.status, jobs.list_jobs, ...)
|
|
46
|
-
salt
|
|
47
|
-
salt
|
|
46
|
+
salt runner manage.status
|
|
47
|
+
salt runner jobs.list_jobs
|
|
48
48
|
|
|
49
49
|
# Wheel client (master-side, low-level)
|
|
50
|
-
salt
|
|
50
|
+
salt wheel key.list_all
|
|
51
51
|
|
|
52
52
|
# Key management (high-level wrapper around the wheel client)
|
|
53
|
-
salt
|
|
54
|
-
salt
|
|
55
|
-
salt
|
|
56
|
-
salt
|
|
57
|
-
salt
|
|
53
|
+
salt keys list
|
|
54
|
+
salt keys accept <id-or-glob>
|
|
55
|
+
salt keys accept-all
|
|
56
|
+
salt keys reject <id-or-glob>
|
|
57
|
+
salt keys delete <id-or-glob>
|
|
58
58
|
```
|
|
59
59
|
|
|
60
60
|
Any `key=value` argument is parsed as a kwarg to the salt function;
|
|
@@ -11,14 +11,16 @@ readme = "README.md"
|
|
|
11
11
|
license = "MIT"
|
|
12
12
|
keywords = []
|
|
13
13
|
classifiers = ["Programming Language :: Python :: 3"]
|
|
14
|
-
dependencies = [
|
|
14
|
+
dependencies = [
|
|
15
|
+
"typeguard>=4.5.2",
|
|
16
|
+
]
|
|
15
17
|
dynamic = ["version"]
|
|
16
18
|
|
|
17
19
|
[project.optional-dependencies]
|
|
18
20
|
pre-commit = ["pre-commit"]
|
|
19
21
|
|
|
20
22
|
[project.scripts]
|
|
21
|
-
salt
|
|
23
|
+
salt = "salt_api_cli.cli:main"
|
|
22
24
|
|
|
23
25
|
[project.urls]
|
|
24
26
|
Homepage = "https://github.com/sandbox-pokhara/saltapi-cli"
|
|
@@ -1,15 +1,25 @@
|
|
|
1
1
|
"""salt-api-cli — thin Python CLI for salt-api.
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Logs in once with PAM creds, caches the token in
|
|
4
4
|
~/.cache/salt-api-cli/token.json, then invokes the salt-api local/
|
|
5
|
-
runner/wheel clients over HTTPS.
|
|
5
|
+
runner/wheel clients over HTTPS. Depends only on the stdlib plus
|
|
6
|
+
``typeguard`` for validating cached/responded JSON.
|
|
7
|
+
|
|
8
|
+
The cached token self-heals: it is refreshed proactively when its stored
|
|
9
|
+
expiry has passed, and reactively when the server rejects it (HTTP 401 or
|
|
10
|
+
an EAUTH body) — e.g. after the salt-master container restarts and wipes
|
|
11
|
+
its session store. On rejection the CLI discards the token, logs in again,
|
|
12
|
+
and retries the request once before giving up. `--relogin` forces a fresh
|
|
13
|
+
login, `--no-token-cache` skips the cache entirely, and the `logout`
|
|
14
|
+
subcommand discards the cached token.
|
|
6
15
|
|
|
7
16
|
Configuration (later sources override earlier):
|
|
8
17
|
1. ~/.saltapiclirc INI file, [salt-api-cli] section
|
|
9
18
|
2. environment variables SALT_API_URL, SALT_API_USER,
|
|
10
19
|
SALT_API_PASS, SALT_API_INSECURE
|
|
11
20
|
3. command-line flags --url, --user, --password,
|
|
12
|
-
--insecure
|
|
21
|
+
--insecure, --relogin,
|
|
22
|
+
--no-token-cache
|
|
13
23
|
|
|
14
24
|
Any `key=value` argument to local/runner/wheel is parsed as a kwarg to
|
|
15
25
|
the salt function. Anything else is positional.
|
|
@@ -31,11 +41,48 @@ from urllib.error import HTTPError, URLError
|
|
|
31
41
|
from urllib.parse import urlencode
|
|
32
42
|
from urllib.request import Request, urlopen
|
|
33
43
|
|
|
44
|
+
from typeguard import TypeCheckError, check_type
|
|
45
|
+
|
|
34
46
|
CONFIG_FILE = Path.home() / ".saltapiclirc"
|
|
35
47
|
CONFIG_SECTION = "salt-api-cli"
|
|
36
48
|
TOKEN_FILE = Path.home() / ".cache" / "salt-api-cli" / "token.json"
|
|
37
49
|
USER_AGENT = "salt-api-cli/1.0 (Mozilla/5.0 compatible)"
|
|
38
50
|
|
|
51
|
+
# Treat a cached token as already gone this many seconds before its real
|
|
52
|
+
# expiry, so we never send one that lapses mid-flight.
|
|
53
|
+
TOKEN_EXPIRY_MARGIN = 60
|
|
54
|
+
|
|
55
|
+
# Substrings that mark a salt-api JSON body as an auth failure. salt-api
|
|
56
|
+
# usually answers an invalid token with HTTP 401, but it sometimes returns
|
|
57
|
+
# 200 with one of these in the payload instead.
|
|
58
|
+
_AUTH_FAIL_MARKERS = (
|
|
59
|
+
"eauth",
|
|
60
|
+
"no permission",
|
|
61
|
+
"not authorized",
|
|
62
|
+
"authentication denied",
|
|
63
|
+
"failed to authenticate",
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
_AUTH_FAIL_HINT = (
|
|
67
|
+
"salt-api authentication still failed after a fresh login — the "
|
|
68
|
+
"credentials may be wrong or the user lacks permission "
|
|
69
|
+
"(check --user/--password, SALT_API_USER/SALT_API_PASS, or "
|
|
70
|
+
"~/.saltapiclirc)."
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class SaltApiError(Exception):
|
|
75
|
+
"""A salt-api error whose message is safe to show the user verbatim."""
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class AuthError(SaltApiError):
|
|
79
|
+
"""An authentication failure (HTTP 401 or an EAUTH/auth-failure body).
|
|
80
|
+
|
|
81
|
+
Signals that the token in hand was rejected and a re-login should be
|
|
82
|
+
attempted.
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
|
|
39
86
|
# Wheel key.list_all groups minion IDs by acceptance state under these keys.
|
|
40
87
|
KEY_STATUS_LABELS = {
|
|
41
88
|
"minions": "Accepted",
|
|
@@ -51,6 +98,10 @@ class Config:
|
|
|
51
98
|
user: str
|
|
52
99
|
password: str
|
|
53
100
|
insecure: bool
|
|
101
|
+
# Ignore any cached token and log in fresh (the new token is still cached).
|
|
102
|
+
relogin: bool = False
|
|
103
|
+
# Neither read nor write the token cache for this run.
|
|
104
|
+
no_token_cache: bool = False
|
|
54
105
|
|
|
55
106
|
|
|
56
107
|
def _truthy(value: str) -> bool:
|
|
@@ -96,6 +147,8 @@ def _load_config(args: argparse.Namespace) -> Config:
|
|
|
96
147
|
user=user,
|
|
97
148
|
password=password,
|
|
98
149
|
insecure=insecure,
|
|
150
|
+
relogin=bool(getattr(args, "relogin", False)),
|
|
151
|
+
no_token_cache=bool(getattr(args, "no_token_cache", False)),
|
|
99
152
|
)
|
|
100
153
|
|
|
101
154
|
|
|
@@ -115,9 +168,25 @@ def _http(req: Request, cfg: Config) -> dict[str, Any]:
|
|
|
115
168
|
return data
|
|
116
169
|
except HTTPError as e:
|
|
117
170
|
body = e.read().decode(errors="replace")
|
|
118
|
-
|
|
171
|
+
if e.code == 401:
|
|
172
|
+
raise AuthError(f"salt-api 401 {e.reason}: {body}") from e
|
|
173
|
+
raise SaltApiError(f"salt-api {e.code} {e.reason}: {body}") from e
|
|
119
174
|
except URLError as e:
|
|
120
|
-
|
|
175
|
+
raise SaltApiError(f"salt-api unreachable: {e.reason}") from e
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _is_auth_failure(result: dict[str, Any]) -> bool:
|
|
179
|
+
"""True if a 200 response body is actually an EAUTH/auth-failure notice."""
|
|
180
|
+
texts: list[str] = []
|
|
181
|
+
try:
|
|
182
|
+
texts.extend(check_type(result.get("return"), list[str]))
|
|
183
|
+
except TypeCheckError:
|
|
184
|
+
pass # a normal result ("return" is a list of dicts) — not an auth body
|
|
185
|
+
for key in ("error", "status"):
|
|
186
|
+
val = result.get(key)
|
|
187
|
+
if isinstance(val, str):
|
|
188
|
+
texts.append(val)
|
|
189
|
+
return any(m in t.lower() for t in texts for m in _AUTH_FAIL_MARKERS)
|
|
121
190
|
|
|
122
191
|
|
|
123
192
|
def _login(cfg: Config) -> dict[str, Any]:
|
|
@@ -136,37 +205,99 @@ def _login(cfg: Config) -> dict[str, Any]:
|
|
|
136
205
|
return info
|
|
137
206
|
|
|
138
207
|
|
|
139
|
-
def
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
TOKEN_FILE.parent.mkdir(parents=True, exist_ok=True)
|
|
149
|
-
TOKEN_FILE.write_text(json.dumps(info))
|
|
208
|
+
def _read_cached_token(cfg: Config) -> str | None:
|
|
209
|
+
"""Return a still-valid cached token, or None to force a fresh login.
|
|
210
|
+
|
|
211
|
+
Tolerant of a missing, empty, corrupt, or schema-mismatched token.json:
|
|
212
|
+
any problem reading it is treated as "no usable token". A token whose
|
|
213
|
+
`expire` is in the past (within a safety margin) is also discarded.
|
|
214
|
+
"""
|
|
215
|
+
if cfg.relogin or cfg.no_token_cache:
|
|
216
|
+
return None
|
|
150
217
|
try:
|
|
151
|
-
|
|
218
|
+
raw = TOKEN_FILE.read_text()
|
|
219
|
+
except OSError:
|
|
220
|
+
return None
|
|
221
|
+
try:
|
|
222
|
+
cached = check_type(json.loads(raw), dict[str, Any])
|
|
223
|
+
except (json.JSONDecodeError, ValueError, TypeCheckError):
|
|
224
|
+
return None
|
|
225
|
+
token = cached.get("token")
|
|
226
|
+
if not token:
|
|
227
|
+
return None
|
|
228
|
+
try:
|
|
229
|
+
expire = float(cached.get("expire", 0))
|
|
230
|
+
except (TypeError, ValueError):
|
|
231
|
+
return None
|
|
232
|
+
if expire <= time.time() + TOKEN_EXPIRY_MARGIN:
|
|
233
|
+
return None
|
|
234
|
+
return str(token)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def _clear_token() -> None:
|
|
238
|
+
"""Discard the cached token, if any. Never raises."""
|
|
239
|
+
try:
|
|
240
|
+
TOKEN_FILE.unlink()
|
|
152
241
|
except OSError:
|
|
153
242
|
pass
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def _fresh_token(cfg: Config) -> str:
|
|
246
|
+
"""Log in and (unless caching is disabled) persist the new token."""
|
|
247
|
+
info = _login(cfg)
|
|
248
|
+
if not cfg.no_token_cache:
|
|
249
|
+
try:
|
|
250
|
+
TOKEN_FILE.parent.mkdir(parents=True, exist_ok=True)
|
|
251
|
+
TOKEN_FILE.write_text(json.dumps(info))
|
|
252
|
+
os.chmod(TOKEN_FILE, 0o600)
|
|
253
|
+
except OSError:
|
|
254
|
+
pass
|
|
154
255
|
return str(info["token"])
|
|
155
256
|
|
|
156
257
|
|
|
258
|
+
def _get_token(cfg: Config) -> str:
|
|
259
|
+
cached = _read_cached_token(cfg)
|
|
260
|
+
if cached is not None:
|
|
261
|
+
return cached
|
|
262
|
+
return _fresh_token(cfg)
|
|
263
|
+
|
|
264
|
+
|
|
157
265
|
def _call(cfg: Config, client: str, **kwargs: Any) -> dict[str, Any]:
|
|
158
266
|
payload = [{"client": client, **kwargs}]
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
267
|
+
body = json.dumps(payload).encode()
|
|
268
|
+
|
|
269
|
+
def attempt(token: str) -> dict[str, Any]:
|
|
270
|
+
req = Request(
|
|
271
|
+
cfg.url,
|
|
272
|
+
data=body,
|
|
273
|
+
headers={
|
|
274
|
+
"Accept": "application/json",
|
|
275
|
+
"Content-Type": "application/json",
|
|
276
|
+
"X-Auth-Token": token,
|
|
277
|
+
"User-Agent": USER_AGENT,
|
|
278
|
+
},
|
|
279
|
+
)
|
|
280
|
+
return _http(req, cfg)
|
|
281
|
+
|
|
282
|
+
# First try with whatever token we have (cached or freshly minted). A
|
|
283
|
+
# rejected token here means it went stale server-side (expiry, or the
|
|
284
|
+
# salt-master session store was wiped on restart) — not bad credentials.
|
|
285
|
+
try:
|
|
286
|
+
result = attempt(_get_token(cfg))
|
|
287
|
+
if not _is_auth_failure(result):
|
|
288
|
+
return result
|
|
289
|
+
except AuthError:
|
|
290
|
+
pass
|
|
291
|
+
|
|
292
|
+
# Discard the stale token, log in fresh, and retry exactly once.
|
|
293
|
+
_clear_token()
|
|
294
|
+
try:
|
|
295
|
+
result = attempt(_fresh_token(cfg))
|
|
296
|
+
except AuthError as e:
|
|
297
|
+
raise AuthError(f"{_AUTH_FAIL_HINT}\ndetails: {e}") from e
|
|
298
|
+
if _is_auth_failure(result):
|
|
299
|
+
raise AuthError(_AUTH_FAIL_HINT)
|
|
300
|
+
return result
|
|
170
301
|
|
|
171
302
|
|
|
172
303
|
def _split_args(args: list[str]) -> tuple[list[str], dict[str, str]]:
|
|
@@ -254,19 +385,19 @@ def _run_keys(cfg: Config, args: argparse.Namespace) -> None:
|
|
|
254
385
|
|
|
255
386
|
def _build_parser() -> argparse.ArgumentParser:
|
|
256
387
|
parser = argparse.ArgumentParser(
|
|
257
|
-
prog="salt
|
|
388
|
+
prog="salt",
|
|
258
389
|
description="Thin Python CLI for salt-api.",
|
|
259
390
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
260
391
|
epilog=(
|
|
261
392
|
"examples:\n"
|
|
262
|
-
" salt
|
|
263
|
-
" salt
|
|
264
|
-
" salt
|
|
265
|
-
" salt
|
|
266
|
-
" salt
|
|
267
|
-
" salt
|
|
268
|
-
" salt
|
|
269
|
-
" salt
|
|
393
|
+
" salt local '*' test.ping\n"
|
|
394
|
+
" salt local 'bml*' cmd.run 'whoami'\n"
|
|
395
|
+
" salt local 'bml1' cmd.run 'Get-Date' shell=powershell\n"
|
|
396
|
+
" salt runner manage.status\n"
|
|
397
|
+
" salt wheel key.list_all\n"
|
|
398
|
+
" salt keys list\n"
|
|
399
|
+
" salt keys accept '<id-or-glob>'\n"
|
|
400
|
+
" salt keys accept-all\n"
|
|
270
401
|
),
|
|
271
402
|
)
|
|
272
403
|
parser.add_argument("--url", help="salt-api base URL")
|
|
@@ -277,6 +408,17 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
277
408
|
action="store_true",
|
|
278
409
|
help="skip TLS certificate verification",
|
|
279
410
|
)
|
|
411
|
+
parser.add_argument(
|
|
412
|
+
"--relogin",
|
|
413
|
+
action="store_true",
|
|
414
|
+
help="ignore any cached token and log in fresh (re-caches the new token)",
|
|
415
|
+
)
|
|
416
|
+
parser.add_argument(
|
|
417
|
+
"--no-token-cache",
|
|
418
|
+
dest="no_token_cache",
|
|
419
|
+
action="store_true",
|
|
420
|
+
help="do not read or write the token cache for this run",
|
|
421
|
+
)
|
|
280
422
|
|
|
281
423
|
sub = parser.add_subparsers(dest="command", required=True)
|
|
282
424
|
|
|
@@ -306,22 +448,39 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
306
448
|
p_delete = keys_sub.add_parser("delete", help="delete a key by id or glob")
|
|
307
449
|
p_delete.add_argument("match")
|
|
308
450
|
|
|
451
|
+
sub.add_parser("logout", help="discard the cached auth token")
|
|
452
|
+
|
|
309
453
|
return parser
|
|
310
454
|
|
|
311
455
|
|
|
312
456
|
def main() -> None:
|
|
313
457
|
parser = _build_parser()
|
|
314
458
|
args = parser.parse_args()
|
|
459
|
+
|
|
460
|
+
# logout needs no server config — it just drops the local token file.
|
|
461
|
+
if args.command == "logout":
|
|
462
|
+
existed = TOKEN_FILE.exists()
|
|
463
|
+
_clear_token()
|
|
464
|
+
print(
|
|
465
|
+
f"discarded cached token ({TOKEN_FILE})"
|
|
466
|
+
if existed
|
|
467
|
+
else f"no cached token to discard ({TOKEN_FILE})"
|
|
468
|
+
)
|
|
469
|
+
return
|
|
470
|
+
|
|
315
471
|
cfg = _load_config(args)
|
|
316
472
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
473
|
+
try:
|
|
474
|
+
if args.command == "local":
|
|
475
|
+
_run_local(cfg, args)
|
|
476
|
+
elif args.command == "runner":
|
|
477
|
+
_run_client(cfg, "runner", args)
|
|
478
|
+
elif args.command == "wheel":
|
|
479
|
+
_run_client(cfg, "wheel", args)
|
|
480
|
+
elif args.command == "keys":
|
|
481
|
+
_run_keys(cfg, args)
|
|
482
|
+
except SaltApiError as e:
|
|
483
|
+
sys.exit(str(e))
|
|
325
484
|
|
|
326
485
|
|
|
327
486
|
if __name__ == "__main__":
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.1.0"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: salt-api-cli
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.1.0
|
|
4
4
|
Summary: CLI to access salt-api
|
|
5
5
|
Author-email: Pradish Bijukchhe <pradish@sandbox.com.np>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -9,6 +9,7 @@ Project-URL: Issues, https://github.com/sandbox-pokhara/saltapi-cli/issues
|
|
|
9
9
|
Classifier: Programming Language :: Python :: 3
|
|
10
10
|
Requires-Python: >=3.11
|
|
11
11
|
Description-Content-Type: text/markdown
|
|
12
|
+
Requires-Dist: typeguard>=4.5.2
|
|
12
13
|
Provides-Extra: pre-commit
|
|
13
14
|
Requires-Dist: pre-commit; extra == "pre-commit"
|
|
14
15
|
|
|
@@ -52,23 +53,23 @@ certificate verification.
|
|
|
52
53
|
|
|
53
54
|
```
|
|
54
55
|
# Local client — fan out to minions
|
|
55
|
-
salt
|
|
56
|
-
salt
|
|
57
|
-
salt
|
|
56
|
+
salt local '*' test.ping
|
|
57
|
+
salt local 'bml*' cmd.run 'whoami'
|
|
58
|
+
salt local 'bml1' cmd.run 'Get-Date' shell=powershell
|
|
58
59
|
|
|
59
60
|
# Runner client (master-side: manage.status, jobs.list_jobs, ...)
|
|
60
|
-
salt
|
|
61
|
-
salt
|
|
61
|
+
salt runner manage.status
|
|
62
|
+
salt runner jobs.list_jobs
|
|
62
63
|
|
|
63
64
|
# Wheel client (master-side, low-level)
|
|
64
|
-
salt
|
|
65
|
+
salt wheel key.list_all
|
|
65
66
|
|
|
66
67
|
# Key management (high-level wrapper around the wheel client)
|
|
67
|
-
salt
|
|
68
|
-
salt
|
|
69
|
-
salt
|
|
70
|
-
salt
|
|
71
|
-
salt
|
|
68
|
+
salt keys list
|
|
69
|
+
salt keys accept <id-or-glob>
|
|
70
|
+
salt keys accept-all
|
|
71
|
+
salt keys reject <id-or-glob>
|
|
72
|
+
salt keys delete <id-or-glob>
|
|
72
73
|
```
|
|
73
74
|
|
|
74
75
|
Any `key=value` argument is parsed as a kwarg to the salt function;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "1.0.0"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|