Skip to content

Commit 7d259e8

Browse files
Merge remote-tracking branch 'origin/develop' into SDK-30/nosemgrep
2 parents 7500539 + 3d116ba commit 7d259e8

File tree

10 files changed

+131
-24
lines changed

10 files changed

+131
-24
lines changed

.github/workflows/test.yml

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name: Python CI
22

33
on:
4-
[ push, pull_request, workflow_dispatch ]
4+
[ push, workflow_dispatch ]
55

66
jobs:
77
build:
@@ -11,11 +11,22 @@ jobs:
1111
matrix:
1212
os:
1313
- ubuntu-latest
14-
python: [ 3.7, 3.9, 3.13]
14+
python: [ 3.9, 3.13 ]
1515
splunk-version:
1616
- "8.1"
1717
- "8.2"
1818
- "latest"
19+
include:
20+
- os: ubuntu-22.04
21+
python: 3.7
22+
splunk-version: "8.1"
23+
- os: ubuntu-22.04
24+
python: 3.7
25+
splunk-version: "8.2"
26+
- os: ubuntu-22.04
27+
python: 3.7
28+
splunk-version: "latest"
29+
1930
fail-fast: false
2031

2132
steps:

splunklib/binding.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@
3939
from http import client
4040
from http.cookies import SimpleCookie
4141
from xml.etree.ElementTree import XML, ParseError
42-
from splunklib.data import record
43-
from splunklib import __version__
42+
from .data import record
43+
from . import __version__
4444

4545

4646
logger = logging.getLogger(__name__)

splunklib/client.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,9 @@
6868
from time import sleep
6969
from urllib import parse
7070

71-
from splunklib import data
72-
from splunklib.data import record
73-
from splunklib.binding import (AuthenticationError, Context, HTTPError, UrlEncoded,
71+
from . import data
72+
from .data import record
73+
from .binding import (AuthenticationError, Context, HTTPError, UrlEncoded,
7474
_encode, _make_cookie_header, _NoAuthenticationToken,
7575
namespace)
7676

@@ -779,7 +779,7 @@ def get_api_version(self, path):
779779
# For example, "/services/search/jobs" is using API v1
780780
api_version = 1
781781

782-
versionSearch = re.search('(?:servicesNS\/[^/]+\/[^/]+|services)\/[^/]+\/v(\d+)\/', path)
782+
versionSearch = re.search(r'(?:servicesNS\/[^/]+\/[^/]+|services)\/[^/]+\/v(\d+)\/', path)
783783
if versionSearch:
784784
api_version = int(versionSearch.group(1))
785785

@@ -3999,4 +3999,4 @@ def batch_save(self, *documents):
39993999
data = json.dumps(documents)
40004000

40014001
return json.loads(
4002-
self._post('batch_save', headers=KVStoreCollectionData.JSON_HEADER, body=data).body.read().decode('utf-8'))
4002+
self._post('batch_save', headers=KVStoreCollectionData.JSON_HEADER, body=data).body.read().decode('utf-8'))

splunklib/modularinput/event.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from io import TextIOBase
1616
import xml.etree.ElementTree as ET
1717

18-
from splunklib.utils import ensure_str
18+
from ..utils import ensure_str
1919

2020

2121
class Event:

splunklib/modularinput/event_writer.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
import sys
1616
import traceback
1717

18-
from splunklib.utils import ensure_str
18+
from ..utils import ensure_str
1919
from .event import ET
2020

2121

splunklib/searchcommands/internals.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -554,7 +554,7 @@ def write_record(self, record):
554554

555555
def write_records(self, records):
556556
self._ensure_validity()
557-
records = list(records)
557+
records = [] if records is NotImplemented else list(records)
558558
write_record = self._write_record
559559
for record in records:
560560
write_record(record)

splunklib/searchcommands/reporting_command.py

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -77,21 +77,26 @@ def map(self, records):
7777
"""
7878
return NotImplemented
7979

80-
def prepare(self):
81-
82-
phase = self.phase
80+
def _has_custom_method(self, method_name):
81+
method = getattr(self.__class__, method_name, None)
82+
base_method = getattr(ReportingCommand, method_name, None)
83+
return callable(method) and (method is not base_method)
8384

84-
if phase == 'map':
85-
# noinspection PyUnresolvedReferences
86-
self._configuration = self.map.ConfigurationSettings(self)
85+
def prepare(self):
86+
if self.phase == 'map':
87+
if self._has_custom_method('map'):
88+
phase_method = getattr(self.__class__, 'map')
89+
self._configuration = phase_method.ConfigurationSettings(self)
90+
else:
91+
self._configuration = self.ConfigurationSettings(self)
8792
return
8893

89-
if phase == 'reduce':
94+
if self.phase == 'reduce':
9095
streaming_preop = chain((self.name, 'phase="map"', str(self._options)), self.fieldnames)
9196
self._configuration.streaming_preop = ' '.join(streaming_preop)
9297
return
9398

94-
raise RuntimeError(f'Unrecognized reporting command phase: {json_encode_string(str(phase))}')
99+
raise RuntimeError(f'Unrecognized reporting command phase: {json_encode_string(str(self.phase))}')
95100

96101
def reduce(self, records):
97102
""" Override this method to produce a reporting data structure.

splunklib/searchcommands/search_command.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,8 @@
3434
from urllib.parse import urlsplit
3535
from warnings import warn
3636
from xml.etree import ElementTree
37-
from splunklib.utils import ensure_str
38-
3937

4038
# Relative imports
41-
import splunklib
4239
from . import Boolean, Option, environment
4340
from .internals import (
4441
CommandLineParser,
@@ -53,6 +50,7 @@
5350
RecordWriterV2,
5451
json_encode_string)
5552
from ..client import Service
53+
from ..utils import ensure_str
5654

5755

5856
# ----------------------------------------------------------------------------------------------------------------------
@@ -342,24 +340,29 @@ def service(self):
342340
of :code:`None` is returned.
343341
344342
"""
343+
345344
if self._service is not None:
346345
return self._service
347346

348347
metadata = self._metadata
349348

350349
if metadata is None:
350+
self.logger.warning("Missing metadata for service creation.")
351351
return None
352352

353353
try:
354354
searchinfo = self._metadata.searchinfo
355355
except AttributeError:
356+
self.logger.warning("Missing searchinfo in metadata for service creation.")
356357
return None
357358

358359
splunkd_uri = searchinfo.splunkd_uri
359360

360-
if splunkd_uri is None:
361+
if splunkd_uri is None or splunkd_uri == "" or splunkd_uri == " ":
362+
self.logger.warning(f"Incorrect value for Splunkd URI: {splunkd_uri!r} in metadata")
361363
return None
362364

365+
363366
uri = urlsplit(splunkd_uri, allow_fragments=False)
364367

365368
self._service = Service(

tests/searchcommands/test_reporting_command.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,42 @@ def reduce(self, records):
3232
data = list(data_chunk.data)
3333
assert len(data) == 1
3434
assert int(data[0]['sum']) == sum(range(0, 10))
35+
36+
37+
def test_simple_reporting_command_with_map():
38+
@searchcommands.Configuration()
39+
class MapAndReduceReportingCommand(searchcommands.ReportingCommand):
40+
def map(self, records):
41+
for record in records:
42+
record["value"] = str(int(record["value"]) * 2)
43+
yield record
44+
45+
def reduce(self, records):
46+
total = 0
47+
for record in records:
48+
total += int(record["value"])
49+
yield {"sum": total}
50+
51+
cmd = MapAndReduceReportingCommand()
52+
ifile = io.BytesIO()
53+
54+
input_data = [{"value": str(i)} for i in range(5)]
55+
56+
mapped_data = list(cmd.map(input_data))
57+
58+
ifile.write(chunky.build_getinfo_chunk())
59+
ifile.write(chunky.build_data_chunk(mapped_data))
60+
ifile.seek(0)
61+
62+
ofile = io.BytesIO()
63+
cmd._process_protocol_v2([], ifile, ofile)
64+
65+
ofile.seek(0)
66+
chunk_stream = chunky.ChunkedDataStream(ofile)
67+
chunk_stream.read_chunk()
68+
data_chunk = chunk_stream.read_chunk()
69+
assert data_chunk.meta['finished'] is True
70+
71+
result = list(data_chunk.data)
72+
expected_sum = sum(i * 2 for i in range(5))
73+
assert int(result[0]["sum"]) == expected_sum

tests/searchcommands/test_search_command.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,17 @@
2222
import codecs
2323
import os
2424
import re
25+
import logging
2526

2627
from io import TextIOWrapper
28+
from unittest.mock import MagicMock, patch
2729

2830
import pytest
2931

3032
import splunklib
3133
from splunklib.searchcommands import Configuration, StreamingCommand
3234
from splunklib.searchcommands.decorators import ConfigurationSetting, Option
35+
from splunklib.searchcommands.internals import ObjectView
3336
from splunklib.searchcommands.search_command import SearchCommand
3437
from splunklib.client import Service
3538
from splunklib.utils import ensure_binary
@@ -265,6 +268,52 @@ def test_process_scpv2(self):
265268

266269
_package_directory = os.path.dirname(os.path.abspath(__file__))
267270

271+
class TestSearchCommandService(TestCase):
272+
def setUp(self):
273+
TestCase.setUp(self)
274+
self.command = SearchCommand()
275+
console_handler = logging.StreamHandler()
276+
console_handler.setLevel(logging.WARNING)
277+
self.command.logger.addHandler(console_handler)
278+
279+
def test_service_exists(self):
280+
self.command._service = Service()
281+
self.assertIsNotNone(self.command.service)
282+
283+
def test_service_not_exists(self):
284+
self.assertIsNone(self.command.service)
285+
286+
def test_missing_metadata(self):
287+
with self.assertLogs(self.command.logger, level='WARNING') as log:
288+
service = self.command.service
289+
self.assertIsNone(service)
290+
self.assertTrue(any("Missing metadata for service creation." in message for message in log.output))
291+
292+
def test_missing_searchinfo(self):
293+
with self.assertLogs(self.command.logger, level='WARNING') as log:
294+
self.command._metadata = ObjectView({})
295+
self.assertIsNone(self.command.service)
296+
self.assertTrue(any("Missing searchinfo in metadata for service creation." in message for message in log.output))
297+
298+
299+
def test_missing_splunkd_uri(self):
300+
with self.assertLogs(self.command.logger, level='WARNING') as log:
301+
metadata = ObjectView({"searchinfo": ObjectView({"splunkd_uri": ""})})
302+
self.command._metadata = metadata
303+
self.assertIsNone(self.command.service)
304+
self.assertTrue(any("Incorrect value for Splunkd URI: '' in metadata" in message for message in log.output))
305+
306+
307+
308+
def test_service_returns_valid_service_object(self):
309+
metadata = ObjectView({"searchinfo":ObjectView({"splunkd_uri":"https://127.0.0.1:8089",
310+
"session_key":"mock_session_key",
311+
"app":"search",
312+
})})
313+
self.command._metadata = metadata
314+
self.assertIsInstance(self.command.service, Service)
315+
316+
268317

269318
if __name__ == "__main__":
270319
main()

0 commit comments

Comments
 (0)