splunkctl 0.4.0__tar.gz → 0.4.1__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.
- {splunkctl-0.4.0 → splunkctl-0.4.1}/PKG-INFO +1 -1
- {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/__init__.py +1 -1
- {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/commands/common.py +23 -0
- {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/commands/conf.py +13 -17
- {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/commands/knowledge.py +15 -32
- {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/commands/lookups.py +3 -13
- {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/commands/rules.py +1 -1
- {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/commands/rules_io.py +3 -17
- {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/commands/search.py +1 -1
- {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/commands/state_io.py +2 -1
- {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/commands/users.py +19 -14
- {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/skill/SKILL.md +4 -3
- {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl.egg-info/PKG-INFO +1 -1
- {splunkctl-0.4.0 → splunkctl-0.4.1}/tests/test_version.py +1 -1
- {splunkctl-0.4.0 → splunkctl-0.4.1}/LICENSE +0 -0
- {splunkctl-0.4.0 → splunkctl-0.4.1}/README.md +0 -0
- {splunkctl-0.4.0 → splunkctl-0.4.1}/pyproject.toml +0 -0
- {splunkctl-0.4.0 → splunkctl-0.4.1}/setup.cfg +0 -0
- {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/__main__.py +0 -0
- {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/client.py +0 -0
- {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/commands/__init__.py +0 -0
- {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/commands/alerts.py +0 -0
- {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/commands/apps.py +0 -0
- {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/commands/audit.py +0 -0
- {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/commands/audit_parse.py +0 -0
- {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/commands/commands_meta.py +0 -0
- {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/commands/conf_ops.py +0 -0
- {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/commands/config_cmd.py +0 -0
- {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/commands/dashboards.py +0 -0
- {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/commands/datamodels.py +0 -0
- {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/commands/doctor.py +0 -0
- {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/commands/es.py +0 -0
- {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/commands/hec.py +0 -0
- {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/commands/indexes.py +0 -0
- {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/commands/info.py +0 -0
- {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/commands/inputs.py +0 -0
- {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/commands/kvstore.py +0 -0
- {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/commands/lookups_wiring.py +0 -0
- {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/commands/parsers.py +0 -0
- {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/commands/parsers_io.py +0 -0
- {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/commands/server.py +0 -0
- {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/commands/skill_cmd.py +0 -0
- {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/commands/state.py +0 -0
- {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/commands/state_io_blobs.py +0 -0
- {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/commands/state_io_confs.py +0 -0
- {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/commands/state_types.py +0 -0
- {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/config.py +0 -0
- {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/errors.py +0 -0
- {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/guard.py +0 -0
- {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/main.py +0 -0
- {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/output.py +0 -0
- {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl.egg-info/SOURCES.txt +0 -0
- {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl.egg-info/dependency_links.txt +0 -0
- {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl.egg-info/entry_points.txt +0 -0
- {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl.egg-info/requires.txt +0 -0
- {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl.egg-info/top_level.txt +0 -0
- {splunkctl-0.4.0 → splunkctl-0.4.1}/tests/test_client.py +0 -0
- {splunkctl-0.4.0 → splunkctl-0.4.1}/tests/test_config.py +0 -0
- {splunkctl-0.4.0 → splunkctl-0.4.1}/tests/test_guard.py +0 -0
- {splunkctl-0.4.0 → splunkctl-0.4.1}/tests/test_main_errors.py +0 -0
- {splunkctl-0.4.0 → splunkctl-0.4.1}/tests/test_main_hoisting.py +0 -0
- {splunkctl-0.4.0 → splunkctl-0.4.1}/tests/test_output.py +0 -0
|
@@ -354,6 +354,29 @@ _REQUIRED_ACTION_FIELDS: dict[str, str] = {
|
|
|
354
354
|
}
|
|
355
355
|
|
|
356
356
|
|
|
357
|
+
def app_scope(app: str | None) -> dict[str, str]:
|
|
358
|
+
"""Build the ``app``/``owner`` kwargs for an app-scoped list call.
|
|
359
|
+
|
|
360
|
+
Unscoped by default (the connection's current namespace); ``owner="-"``
|
|
361
|
+
when an app is given so app-private stanzas owned by other users aren't
|
|
362
|
+
silently excluded.
|
|
363
|
+
"""
|
|
364
|
+
return {} if app is None else {"app": app, "owner": "-"}
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
def trunc(value: str, limit: int = 60) -> str:
|
|
368
|
+
"""Shorten a long text field for table display.
|
|
369
|
+
|
|
370
|
+
Never emits a bare ellipsis — a truncated value always says how many
|
|
371
|
+
characters were hidden, e.g. ``foo… [+57 chars]``.
|
|
372
|
+
"""
|
|
373
|
+
if len(value) <= limit:
|
|
374
|
+
return value
|
|
375
|
+
kept = value[: limit - 1]
|
|
376
|
+
hidden = len(value) - len(kept)
|
|
377
|
+
return f"{kept}… [+{hidden} chars]"
|
|
378
|
+
|
|
379
|
+
|
|
357
380
|
def warn_missing_action_fields(
|
|
358
381
|
actions: str,
|
|
359
382
|
kwargs: dict[str, Any],
|
|
@@ -15,7 +15,7 @@ import click
|
|
|
15
15
|
from splunkctl import guard, output
|
|
16
16
|
from splunkctl.client import get_client
|
|
17
17
|
from splunkctl.commands import conf_ops
|
|
18
|
-
from splunkctl.commands.common import fetch_page, list_options, parse_set
|
|
18
|
+
from splunkctl.commands.common import app_scope, fetch_page, list_options, parse_set
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
def _not_found_message(exc: KeyError, file: str, stanza: str) -> str:
|
|
@@ -35,18 +35,6 @@ def _not_found_message(exc: KeyError, file: str, stanza: str) -> str:
|
|
|
35
35
|
return f"Stanza '{stanza}' not found in {file}.conf."
|
|
36
36
|
|
|
37
37
|
|
|
38
|
-
def _app_scope(app: str | None) -> dict[str, str]:
|
|
39
|
-
"""Build the ``app``/``owner`` kwargs for an app-scoped list call.
|
|
40
|
-
|
|
41
|
-
Mirrors the ``rules``/``dashboards`` scoping convention: unscoped by
|
|
42
|
-
default (matches ``parsers``' existing default, i.e. whatever the
|
|
43
|
-
connection's current namespace resolves to), ``owner="-"`` when an
|
|
44
|
-
app is given so app-private stanzas owned by other users aren't
|
|
45
|
-
silently excluded.
|
|
46
|
-
"""
|
|
47
|
-
return {} if app is None else {"app": app, "owner": "-"}
|
|
48
|
-
|
|
49
|
-
|
|
50
38
|
@click.group("conf")
|
|
51
39
|
def conf_group() -> None:
|
|
52
40
|
"""Generic conf file/stanza editor (any conf, including sensitive ones)."""
|
|
@@ -70,7 +58,7 @@ def files(
|
|
|
70
58
|
) -> None:
|
|
71
59
|
"""List conf files known to the server."""
|
|
72
60
|
client = get_client(ctx)
|
|
73
|
-
scope =
|
|
61
|
+
scope = app_scope(app)
|
|
74
62
|
items = fetch_page(
|
|
75
63
|
lambda **pg: client.service.confs.list(**scope, **pg),
|
|
76
64
|
limit=limit,
|
|
@@ -102,7 +90,7 @@ def list_stanzas(
|
|
|
102
90
|
) -> None:
|
|
103
91
|
"""List stanzas in a conf file."""
|
|
104
92
|
client = get_client(ctx)
|
|
105
|
-
scope =
|
|
93
|
+
scope = app_scope(app)
|
|
106
94
|
items = fetch_page(
|
|
107
95
|
lambda **pg: client.service.confs[file].list(**scope, **pg),
|
|
108
96
|
limit=limit,
|
|
@@ -123,13 +111,21 @@ def list_stanzas(
|
|
|
123
111
|
@conf_group.command("get")
|
|
124
112
|
@click.argument("file")
|
|
125
113
|
@click.argument("stanza")
|
|
114
|
+
@click.option(
|
|
115
|
+
"--app",
|
|
116
|
+
default=None,
|
|
117
|
+
help="App to resolve the stanza in, when the same stanza exists in "
|
|
118
|
+
"more than one app (default: current namespace).",
|
|
119
|
+
)
|
|
126
120
|
@click.option("--key", default=None, help="Show only this key's value.")
|
|
127
121
|
@click.pass_context
|
|
128
|
-
def get(
|
|
122
|
+
def get(
|
|
123
|
+
ctx: click.Context, file: str, stanza: str, app: str | None, key: str | None
|
|
124
|
+
) -> None:
|
|
129
125
|
"""Show a conf stanza's keys, or one key's value with --key."""
|
|
130
126
|
client = get_client(ctx)
|
|
131
127
|
try:
|
|
132
|
-
entity = conf_ops.get_stanza(client, file, stanza)
|
|
128
|
+
entity = conf_ops.get_stanza(client, file, stanza, app=app)
|
|
133
129
|
except KeyError as exc:
|
|
134
130
|
output.error(_not_found_message(exc, file, stanza), kind="not_found")
|
|
135
131
|
ctx.exit(1)
|
|
@@ -24,37 +24,12 @@ import click
|
|
|
24
24
|
from splunkctl import guard, output
|
|
25
25
|
from splunkctl.client import get_client
|
|
26
26
|
from splunkctl.commands import conf_ops
|
|
27
|
-
from splunkctl.commands.common import fetch_page, list_options
|
|
27
|
+
from splunkctl.commands.common import app_scope, fetch_page, list_options, trunc
|
|
28
28
|
|
|
29
|
-
_TRUNC = 60
|
|
30
29
|
# Splunk's own naming for a parameterized macro stanza: `name(argcount)`.
|
|
31
30
|
_ARG_FORM = re.compile(r"^(?P<base>.+)\((?P<n>\d+)\)$")
|
|
32
31
|
|
|
33
32
|
|
|
34
|
-
def _trunc(value: str, *, truncate: bool) -> str:
|
|
35
|
-
"""Shorten a long text field for table display; never for JSON.
|
|
36
|
-
|
|
37
|
-
Never emits a bare ellipsis — a truncated value always says how
|
|
38
|
-
many characters were hidden, matching the house style used for
|
|
39
|
-
rule-diff previews.
|
|
40
|
-
"""
|
|
41
|
-
if not truncate or len(value) <= _TRUNC:
|
|
42
|
-
return value
|
|
43
|
-
kept = value[: _TRUNC - 1]
|
|
44
|
-
hidden = len(value) - len(kept)
|
|
45
|
-
return f"{kept}… [+{hidden} chars]"
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
def _app_scope(app: str | None) -> dict[str, str]:
|
|
49
|
-
"""Build the ``app``/``owner`` kwargs for an app-scoped list call.
|
|
50
|
-
|
|
51
|
-
Unscoped by default (matches the connection's current namespace);
|
|
52
|
-
``owner="-"`` when an app is given so app-private stanzas owned by
|
|
53
|
-
other users aren't silently excluded — same convention as `conf`.
|
|
54
|
-
"""
|
|
55
|
-
return {} if app is None else {"app": app, "owner": "-"}
|
|
56
|
-
|
|
57
|
-
|
|
58
33
|
# --------------------------------------------------------------------------
|
|
59
34
|
# macros
|
|
60
35
|
# --------------------------------------------------------------------------
|
|
@@ -84,7 +59,7 @@ def macros_list(
|
|
|
84
59
|
) -> None:
|
|
85
60
|
"""List macros with their definitions."""
|
|
86
61
|
client = get_client(ctx)
|
|
87
|
-
scope =
|
|
62
|
+
scope = app_scope(app)
|
|
88
63
|
items = fetch_page(
|
|
89
64
|
lambda **pg: client.service.confs["macros"].list(**scope, **pg),
|
|
90
65
|
limit=limit,
|
|
@@ -95,7 +70,11 @@ def macros_list(
|
|
|
95
70
|
rows: list[dict[str, Any]] = [
|
|
96
71
|
{
|
|
97
72
|
"name": s.name,
|
|
98
|
-
"definition":
|
|
73
|
+
"definition": (
|
|
74
|
+
trunc(s.content.get("definition", ""))
|
|
75
|
+
if truncate
|
|
76
|
+
else s.content.get("definition", "")
|
|
77
|
+
),
|
|
99
78
|
"args": s.content.get("args", ""),
|
|
100
79
|
"app": dict(s.access).get("app", ""),
|
|
101
80
|
}
|
|
@@ -125,7 +104,7 @@ def _resolve_macro(client: Any, name: str, app: str | None) -> Any:
|
|
|
125
104
|
raise
|
|
126
105
|
candidates = [
|
|
127
106
|
(int(m["n"]), s)
|
|
128
|
-
for s in client.service.confs["macros"].list(**
|
|
107
|
+
for s in client.service.confs["macros"].list(**app_scope(app))
|
|
129
108
|
if (m := _ARG_FORM.match(s.name)) and m["base"] == name
|
|
130
109
|
]
|
|
131
110
|
if not candidates:
|
|
@@ -241,7 +220,7 @@ def eventtypes_list(
|
|
|
241
220
|
) -> None:
|
|
242
221
|
"""List eventtypes with their search and disabled state."""
|
|
243
222
|
client = get_client(ctx)
|
|
244
|
-
scope =
|
|
223
|
+
scope = app_scope(app)
|
|
245
224
|
items = fetch_page(
|
|
246
225
|
lambda **pg: client.service.confs["eventtypes"].list(**scope, **pg),
|
|
247
226
|
limit=limit,
|
|
@@ -252,7 +231,11 @@ def eventtypes_list(
|
|
|
252
231
|
rows: list[dict[str, Any]] = [
|
|
253
232
|
{
|
|
254
233
|
"name": s.name,
|
|
255
|
-
"search":
|
|
234
|
+
"search": (
|
|
235
|
+
trunc(s.content.get("search", ""))
|
|
236
|
+
if truncate
|
|
237
|
+
else s.content.get("search", "")
|
|
238
|
+
),
|
|
256
239
|
"app": dict(s.access).get("app", ""),
|
|
257
240
|
"disabled": s.content.get("disabled", ""),
|
|
258
241
|
}
|
|
@@ -343,7 +326,7 @@ def tags_list(
|
|
|
343
326
|
use ``tags get`` for a stanza's full enabled/disabled breakdown.
|
|
344
327
|
"""
|
|
345
328
|
client = get_client(ctx)
|
|
346
|
-
scope =
|
|
329
|
+
scope = app_scope(app)
|
|
347
330
|
items = fetch_page(
|
|
348
331
|
lambda **pg: client.service.confs["tags"].list(**scope, **pg),
|
|
349
332
|
limit=limit,
|
|
@@ -18,17 +18,7 @@ import click
|
|
|
18
18
|
from splunkctl import guard, output
|
|
19
19
|
from splunkctl.client import get_client
|
|
20
20
|
from splunkctl.commands import conf_ops, lookups_wiring
|
|
21
|
-
from splunkctl.commands.common import fetch_page, list_options
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
def _app_scope(app: str | None) -> dict[str, str]:
|
|
25
|
-
"""Build the ``app``/``owner`` kwargs for an app-scoped list call.
|
|
26
|
-
|
|
27
|
-
Mirrors ``conf``/``knowledge``'s convention: unscoped by default
|
|
28
|
-
(current namespace), ``owner="-"`` when an app is given so
|
|
29
|
-
app-private stanzas owned by other users aren't silently excluded.
|
|
30
|
-
"""
|
|
31
|
-
return {} if app is None else {"app": app, "owner": "-"}
|
|
21
|
+
from splunkctl.commands.common import app_scope, fetch_page, list_options
|
|
32
22
|
|
|
33
23
|
|
|
34
24
|
@click.group("lookups")
|
|
@@ -247,7 +237,7 @@ def _warn_if_file_missing(client: Any, file_name: str, app: str | None) -> None:
|
|
|
247
237
|
"""
|
|
248
238
|
try:
|
|
249
239
|
matches = client.service.lookup_table_files.list(
|
|
250
|
-
search=f"name={file_name}", count=1, **
|
|
240
|
+
search=f"name={file_name}", count=1, **app_scope(app)
|
|
251
241
|
)
|
|
252
242
|
except Exception:
|
|
253
243
|
return
|
|
@@ -459,7 +449,7 @@ def list_definitions(
|
|
|
459
449
|
"""
|
|
460
450
|
client = get_client(ctx)
|
|
461
451
|
items = fetch_page(
|
|
462
|
-
lambda **pg: client.service.confs["transforms"].list(**
|
|
452
|
+
lambda **pg: client.service.confs["transforms"].list(**app_scope(app), **pg),
|
|
463
453
|
limit=limit,
|
|
464
454
|
offset=offset,
|
|
465
455
|
name_filter=name_filter,
|
|
@@ -423,7 +423,7 @@ def test_rule(
|
|
|
423
423
|
while not job.is_done():
|
|
424
424
|
if time.monotonic() > deadline:
|
|
425
425
|
job.cancel()
|
|
426
|
-
output.error(f"Test run timed out after {timeout}s.")
|
|
426
|
+
output.error(f"Test run timed out after {timeout}s.", kind="timeout")
|
|
427
427
|
ctx.exit(1)
|
|
428
428
|
return
|
|
429
429
|
time.sleep(0.5)
|
|
@@ -10,6 +10,7 @@ import yaml
|
|
|
10
10
|
|
|
11
11
|
from splunkctl import guard, output
|
|
12
12
|
from splunkctl.client import get_client
|
|
13
|
+
from splunkctl.commands.common import trunc
|
|
13
14
|
|
|
14
15
|
_EXPORT_FIELDS = (
|
|
15
16
|
"search",
|
|
@@ -47,21 +48,6 @@ _READONLY_KEYS = frozenset(
|
|
|
47
48
|
}
|
|
48
49
|
)
|
|
49
50
|
|
|
50
|
-
_TRUNC = 60
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
def _trunc(val: str) -> str:
|
|
54
|
-
"""Truncate for the text preview, with an explicit hidden-length marker.
|
|
55
|
-
|
|
56
|
-
Never emits a bare ``…`` — a truncated value always says how many
|
|
57
|
-
characters were hidden, e.g. ``foo… [+57 chars]``.
|
|
58
|
-
"""
|
|
59
|
-
if len(val) <= _TRUNC:
|
|
60
|
-
return val
|
|
61
|
-
kept = val[: _TRUNC - 1]
|
|
62
|
-
hidden = len(val) - len(kept)
|
|
63
|
-
return f"{kept}… [+{hidden} chars]"
|
|
64
|
-
|
|
65
51
|
|
|
66
52
|
class FieldChange(TypedDict):
|
|
67
53
|
"""One field's before/after value in a rule diff."""
|
|
@@ -243,7 +229,7 @@ def _preview_lines(d: RuleDiff) -> list[str]:
|
|
|
243
229
|
if kind == "update":
|
|
244
230
|
lines = [f" update: {name}"]
|
|
245
231
|
lines.extend(
|
|
246
|
-
f" {c['field']}: {
|
|
232
|
+
f" {c['field']}: {trunc(str(c['old']))} -> {trunc(c['new'])}"
|
|
247
233
|
for c in d["changes"]
|
|
248
234
|
)
|
|
249
235
|
return lines
|
|
@@ -291,7 +277,7 @@ def _plan_rule(svc: Any, rule: Any, *, update: bool) -> tuple[str, list[str]]:
|
|
|
291
277
|
diff = _changes(ss, spl, kwargs)
|
|
292
278
|
if not diff:
|
|
293
279
|
return f"unchanged:{name}", []
|
|
294
|
-
lines = [f" {k}: {
|
|
280
|
+
lines = [f" {k}: {trunc(old)} -> {trunc(new)}" for k, (old, new) in diff.items()]
|
|
295
281
|
return f"update:{name}", lines
|
|
296
282
|
|
|
297
283
|
|
|
@@ -114,7 +114,7 @@ def run_search(
|
|
|
114
114
|
while not job.is_done():
|
|
115
115
|
if time.monotonic() > deadline:
|
|
116
116
|
job.cancel()
|
|
117
|
-
output.error(f"Search timed out after {timeout}s.")
|
|
117
|
+
output.error(f"Search timed out after {timeout}s.", kind="timeout")
|
|
118
118
|
ctx.exit(1)
|
|
119
119
|
return
|
|
120
120
|
time.sleep(0.5)
|
|
@@ -86,7 +86,8 @@ def _drift_from_rule_diff(rd: RuleDiff) -> DriftEntry:
|
|
|
86
86
|
name = rd["name"]
|
|
87
87
|
if name is None:
|
|
88
88
|
raise ValueError("cannot build a drift entry for a skipped (nameless) rule doc")
|
|
89
|
-
|
|
89
|
+
change = _RULE_CHANGE.get(rd["kind"], "unchanged")
|
|
90
|
+
entry: DriftEntry = {"name": name, "change": change}
|
|
90
91
|
if rd["changes"]:
|
|
91
92
|
entry["fields"] = rd["changes"]
|
|
92
93
|
return entry
|
|
@@ -121,28 +121,33 @@ def get_user(ctx: click.Context, name: str) -> None:
|
|
|
121
121
|
# --- roles sub-group ---
|
|
122
122
|
|
|
123
123
|
|
|
124
|
-
@users_group.group("roles"
|
|
124
|
+
@users_group.group("roles")
|
|
125
|
+
@click.pass_context
|
|
126
|
+
def roles_group(ctx: click.Context) -> None:
|
|
127
|
+
"""Manage roles."""
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
@roles_group.command("list")
|
|
125
131
|
@list_options
|
|
126
132
|
@click.pass_context
|
|
127
|
-
def
|
|
133
|
+
def list_roles(
|
|
128
134
|
ctx: click.Context,
|
|
129
135
|
*,
|
|
130
136
|
limit: int | None,
|
|
131
137
|
offset: int,
|
|
132
138
|
name_filter: str | None,
|
|
133
139
|
) -> None:
|
|
134
|
-
"""
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
roles
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
output.render(ctx, rows, empty="No roles found.")
|
|
140
|
+
"""List all roles."""
|
|
141
|
+
client = get_client(ctx)
|
|
142
|
+
roles = fetch_page(
|
|
143
|
+
client.service.roles.list,
|
|
144
|
+
limit=limit,
|
|
145
|
+
offset=offset,
|
|
146
|
+
name_filter=name_filter,
|
|
147
|
+
)
|
|
148
|
+
truncate = output.is_table(ctx)
|
|
149
|
+
rows = [_role_row(r, truncate=truncate) for r in roles]
|
|
150
|
+
output.render(ctx, rows, empty="No roles found.")
|
|
146
151
|
|
|
147
152
|
|
|
148
153
|
@roles_group.command("get")
|
|
@@ -118,7 +118,7 @@ connection, preserving lazy auth even for a blocked dry-run.
|
|
|
118
118
|
|
|
119
119
|
Every list surface (`rules list`, `alerts list`, `dashboards list`,
|
|
120
120
|
`indexes list`, `inputs list`, `lookups list`, `hec list`, `apps list`,
|
|
121
|
-
`users list`, `users roles`, `parsers sourcetypes`, `parsers extractions`,
|
|
121
|
+
`users list`, `users roles list`, `parsers sourcetypes`, `parsers extractions`,
|
|
122
122
|
`search jobs`) accepts three uniform options:
|
|
123
123
|
|
|
124
124
|
| Option | Meaning |
|
|
@@ -494,6 +494,7 @@ splunkctl conf files # list conf files
|
|
|
494
494
|
splunkctl conf list macros # list stanzas in a conf file
|
|
495
495
|
splunkctl conf list macros --app my_app # scope to one app's stanzas
|
|
496
496
|
splunkctl conf get macros my_macro # full stanza config
|
|
497
|
+
splunkctl conf get macros my_macro --app SES # scope to one app
|
|
497
498
|
splunkctl conf get macros my_macro --key definition # one key only
|
|
498
499
|
splunkctl conf set macros my_macro definition='index=main' --yes # create/update
|
|
499
500
|
splunkctl conf unset macros my_macro definition --yes # clear a key (sets empty)
|
|
@@ -664,7 +665,7 @@ splunkctl users create --name newuser --password 'pass' \
|
|
|
664
665
|
splunkctl users update newuser --roles 'user,power' --yes
|
|
665
666
|
splunkctl users update newuser --password 'newpass' --yes
|
|
666
667
|
splunkctl users delete newuser --yes
|
|
667
|
-
splunkctl users roles
|
|
668
|
+
splunkctl users roles list # list all roles
|
|
668
669
|
splunkctl users roles get admin # role details
|
|
669
670
|
splunkctl users roles create --name myrole --imported-roles user \
|
|
670
671
|
--capabilities 'search,list_inputs' --yes
|
|
@@ -927,7 +928,7 @@ splunkctl indexes list --json \
|
|
|
927
928
|
|
|
928
929
|
```bash
|
|
929
930
|
splunkctl users list --json | jq '.[] | {name, roles, email}'
|
|
930
|
-
splunkctl users roles --json | jq '.[] | {name, capabilities}'
|
|
931
|
+
splunkctl users roles list --json | jq '.[] | {name, capabilities}'
|
|
931
932
|
```
|
|
932
933
|
|
|
933
934
|
### Periodic access recertification (RBAC attestation)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|