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.
Files changed (62) hide show
  1. {splunkctl-0.4.0 → splunkctl-0.4.1}/PKG-INFO +1 -1
  2. {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/__init__.py +1 -1
  3. {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/commands/common.py +23 -0
  4. {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/commands/conf.py +13 -17
  5. {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/commands/knowledge.py +15 -32
  6. {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/commands/lookups.py +3 -13
  7. {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/commands/rules.py +1 -1
  8. {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/commands/rules_io.py +3 -17
  9. {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/commands/search.py +1 -1
  10. {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/commands/state_io.py +2 -1
  11. {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/commands/users.py +19 -14
  12. {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/skill/SKILL.md +4 -3
  13. {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl.egg-info/PKG-INFO +1 -1
  14. {splunkctl-0.4.0 → splunkctl-0.4.1}/tests/test_version.py +1 -1
  15. {splunkctl-0.4.0 → splunkctl-0.4.1}/LICENSE +0 -0
  16. {splunkctl-0.4.0 → splunkctl-0.4.1}/README.md +0 -0
  17. {splunkctl-0.4.0 → splunkctl-0.4.1}/pyproject.toml +0 -0
  18. {splunkctl-0.4.0 → splunkctl-0.4.1}/setup.cfg +0 -0
  19. {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/__main__.py +0 -0
  20. {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/client.py +0 -0
  21. {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/commands/__init__.py +0 -0
  22. {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/commands/alerts.py +0 -0
  23. {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/commands/apps.py +0 -0
  24. {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/commands/audit.py +0 -0
  25. {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/commands/audit_parse.py +0 -0
  26. {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/commands/commands_meta.py +0 -0
  27. {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/commands/conf_ops.py +0 -0
  28. {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/commands/config_cmd.py +0 -0
  29. {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/commands/dashboards.py +0 -0
  30. {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/commands/datamodels.py +0 -0
  31. {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/commands/doctor.py +0 -0
  32. {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/commands/es.py +0 -0
  33. {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/commands/hec.py +0 -0
  34. {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/commands/indexes.py +0 -0
  35. {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/commands/info.py +0 -0
  36. {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/commands/inputs.py +0 -0
  37. {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/commands/kvstore.py +0 -0
  38. {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/commands/lookups_wiring.py +0 -0
  39. {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/commands/parsers.py +0 -0
  40. {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/commands/parsers_io.py +0 -0
  41. {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/commands/server.py +0 -0
  42. {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/commands/skill_cmd.py +0 -0
  43. {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/commands/state.py +0 -0
  44. {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/commands/state_io_blobs.py +0 -0
  45. {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/commands/state_io_confs.py +0 -0
  46. {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/commands/state_types.py +0 -0
  47. {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/config.py +0 -0
  48. {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/errors.py +0 -0
  49. {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/guard.py +0 -0
  50. {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/main.py +0 -0
  51. {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl/output.py +0 -0
  52. {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl.egg-info/SOURCES.txt +0 -0
  53. {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl.egg-info/dependency_links.txt +0 -0
  54. {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl.egg-info/entry_points.txt +0 -0
  55. {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl.egg-info/requires.txt +0 -0
  56. {splunkctl-0.4.0 → splunkctl-0.4.1}/splunkctl.egg-info/top_level.txt +0 -0
  57. {splunkctl-0.4.0 → splunkctl-0.4.1}/tests/test_client.py +0 -0
  58. {splunkctl-0.4.0 → splunkctl-0.4.1}/tests/test_config.py +0 -0
  59. {splunkctl-0.4.0 → splunkctl-0.4.1}/tests/test_guard.py +0 -0
  60. {splunkctl-0.4.0 → splunkctl-0.4.1}/tests/test_main_errors.py +0 -0
  61. {splunkctl-0.4.0 → splunkctl-0.4.1}/tests/test_main_hoisting.py +0 -0
  62. {splunkctl-0.4.0 → splunkctl-0.4.1}/tests/test_output.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: splunkctl
3
- Version: 0.4.0
3
+ Version: 0.4.1
4
4
  Summary: CLI tool for Splunk Enterprise SIEM operations.
5
5
  Author: dannyota
6
6
  License-Expression: Apache-2.0
@@ -1,3 +1,3 @@
1
1
  """CLI tool for Splunk Enterprise SIEM operations."""
2
2
 
3
- __version__ = "0.4.0"
3
+ __version__ = "0.4.1"
@@ -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 = _app_scope(app)
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 = _app_scope(app)
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(ctx: click.Context, file: str, stanza: str, key: str | None) -> None:
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 = _app_scope(app)
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": _trunc(s.content.get("definition", ""), truncate=truncate),
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(**_app_scope(app))
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 = _app_scope(app)
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": _trunc(s.content.get("search", ""), truncate=truncate),
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 = _app_scope(app)
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, **_app_scope(app)
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(**_app_scope(app), **pg),
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']}: {_trunc(str(c['old']))} -> {_trunc(c['new'])}"
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}: {_trunc(old)} -> {_trunc(new)}" for k, (old, new) in diff.items()]
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
- entry: DriftEntry = {"name": name, "change": _RULE_CHANGE[rd["kind"]]}
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", invoke_without_command=True)
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 roles_group(
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
- """Manage roles (bare invocation lists all roles)."""
135
- if ctx.invoked_subcommand is None:
136
- client = get_client(ctx)
137
- roles = fetch_page(
138
- client.service.roles.list,
139
- limit=limit,
140
- offset=offset,
141
- name_filter=name_filter,
142
- )
143
- truncate = output.is_table(ctx)
144
- rows = [_role_row(r, truncate=truncate) for r in roles]
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 # list all 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)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: splunkctl
3
- Version: 0.4.0
3
+ Version: 0.4.1
4
4
  Summary: CLI tool for Splunk Enterprise SIEM operations.
5
5
  Author: dannyota
6
6
  License-Expression: Apache-2.0
@@ -4,4 +4,4 @@ from splunkctl import __version__
4
4
 
5
5
 
6
6
  def test_version() -> None:
7
- assert __version__ == "0.4.0"
7
+ assert __version__ == "0.4.1"
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