Skip to content

Commit c9e1f73

Browse files
feat: add function to get the proxy details (#406)
[ADDON-72372](https://splunk.atlassian.net/browse/ADDON-72372) - Add function to get the proxy details as a dictionary.
1 parent 20271a4 commit c9e1f73

File tree

6 files changed

+377
-24
lines changed

6 files changed

+377
-24
lines changed

solnlib/conf_manager.py

Lines changed: 73 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -21,28 +21,27 @@
2121
import json
2222
import logging
2323
import traceback
24-
from typing import List
24+
from typing import List, Union, Dict, NoReturn
2525

2626
from splunklib import binding, client
2727

2828
from . import splunk_rest_client as rest_client
2929
from .credentials import CredentialManager, CredentialNotExistException
3030
from .utils import retry
31+
from .net_utils import is_valid_port, is_valid_hostname
32+
from .soln_exceptions import (
33+
ConfManagerException,
34+
ConfStanzaNotExistException,
35+
InvalidPortError,
36+
InvalidHostnameError,
37+
)
3138

3239
__all__ = [
33-
"ConfStanzaNotExistException",
3440
"ConfFile",
35-
"ConfManagerException",
3641
"ConfManager",
3742
]
3843

3944

40-
class ConfStanzaNotExistException(Exception):
41-
"""Exception raised by ConfFile class."""
42-
43-
pass
44-
45-
4645
class ConfFile:
4746
"""Configuration file."""
4847

@@ -361,12 +360,6 @@ def reload(self):
361360
self._conf.get("_reload")
362361

363362

364-
class ConfManagerException(Exception):
365-
"""Exception raised by ConfManager class."""
366-
367-
pass
368-
369-
370363
class ConfManager:
371364
"""Configuration file manager.
372365
@@ -557,3 +550,68 @@ def get_log_level(
557550
f"taking {default_log_level} as log level."
558551
)
559552
return default_log_level
553+
554+
555+
def get_proxy_dict(
556+
logger: logging.Logger,
557+
session_key: str,
558+
app_name: str,
559+
conf_name: str,
560+
proxy_stanza: str = "proxy",
561+
**kwargs,
562+
) -> Union[Dict[str, str], NoReturn]:
563+
"""This function returns the proxy settings for the addon from
564+
configuration file.
565+
566+
Arguments:
567+
logger: Logger.
568+
session_key: Splunk access token.
569+
app_name: Add-on name.
570+
conf_name: Configuration file name where logging stanza is.
571+
proxy_stanza: Proxy stanza that would contain the Proxy details
572+
Returns:
573+
A dictionary is returned with stanza details present in the file.
574+
The keys related to `eai` are removed before returning.
575+
576+
Examples:
577+
>>> from solnlib import conf_manager
578+
>>> proxy_details = conf_manager.get_proxy_dict(
579+
>>> logger,
580+
>>> "session_key",
581+
>>> "ADDON_NAME",
582+
>>> "splunk_ta_addon_settings",
583+
>>> )
584+
"""
585+
proxy_dict = {}
586+
try:
587+
cfm = ConfManager(
588+
session_key,
589+
app_name,
590+
realm=f"__REST_CREDENTIAL__#{app_name}#configs/conf-{conf_name}",
591+
)
592+
conf = cfm.get_conf(conf_name)
593+
except Exception:
594+
raise ConfManagerException(f"Failed to fetch configuration file '{conf_name}'.")
595+
else:
596+
try:
597+
proxy_dict = conf.get(proxy_stanza)
598+
except Exception:
599+
raise ConfStanzaNotExistException(
600+
f"Failed to fetch '{proxy_stanza}' from the configuration file '{conf_name}'. "
601+
)
602+
else:
603+
# remove the other fields that are added by ConfFile class
604+
proxy_dict.pop("disabled", None)
605+
proxy_dict.pop("eai:access", None)
606+
proxy_dict.pop("eai:appName", None)
607+
proxy_dict.pop("eai:userName", None)
608+
609+
if "proxy_port" in kwargs:
610+
if not is_valid_port(proxy_dict.get(kwargs["proxy_port"])):
611+
logger.error("Invalid proxy port provided.")
612+
raise InvalidPortError("The provided port is not valid.")
613+
if "proxy_host" in kwargs:
614+
if not is_valid_hostname(proxy_dict.get(kwargs["proxy_host"])):
615+
logger.error("Invalid proxy host provided.")
616+
raise InvalidHostnameError("The provided hostname is not valid.")
617+
return proxy_dict

solnlib/soln_exceptions.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
#
2+
# Copyright 2024 Splunk Inc.
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
#
16+
class ConfManagerException(Exception):
17+
"""Exception raised by ConfManager class."""
18+
19+
pass
20+
21+
22+
class ConfStanzaNotExistException(Exception):
23+
"""Exception raised by ConfFile class."""
24+
25+
pass
26+
27+
28+
class InvalidPortError(ValueError):
29+
"""Exception raised when an invalid proxy port is provided."""
30+
31+
pass
32+
33+
34+
class InvalidHostnameError(ValueError):
35+
"""Exception raised when an invalid proxy hostname is provided."""
36+
37+
pass
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,11 @@
11
[logging]
22
log_level = DEBUG
3+
4+
[proxy]
5+
proxy_enabled =
6+
proxy_type = http
7+
proxy_url = remote_host
8+
proxy_port = 3128
9+
proxy_username =
10+
proxy_password =
11+
proxy_rdns =
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
2+
[invalid_proxy]
3+
proxy_enabled =
4+
proxy_type = http3
5+
proxy_url = remote:host:invalid
6+
proxy_port = 99999
7+
proxy_username =
8+
proxy_password =
9+
proxy_rdns =

tests/integration/test_conf_manager.py

Lines changed: 79 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,20 @@
1515
#
1616

1717
import context
18-
import os.path as op
19-
import sys
2018
import pytest
21-
from solnlib import conf_manager
19+
from solnlib import conf_manager, soln_exceptions
2220
from unittest import mock
2321

2422

25-
sys.path.insert(0, op.dirname(op.dirname(op.abspath(__file__))))
23+
VALID_PROXY_DICT = {
24+
"proxy_enabled": None,
25+
"proxy_type": "http",
26+
"proxy_url": "remote_host",
27+
"proxy_port": "3128",
28+
"proxy_username": None,
29+
"proxy_password": None,
30+
"proxy_rdns": None,
31+
}
2632

2733

2834
def _build_conf_manager(session_key: str) -> conf_manager.ConfManager:
@@ -40,7 +46,7 @@ def test_conf_manager_when_no_conf_then_throw_exception():
4046
session_key = context.get_session_key()
4147
cfm = _build_conf_manager(session_key)
4248

43-
with pytest.raises(conf_manager.ConfManagerException):
49+
with pytest.raises(soln_exceptions.ConfManagerException):
4450
cfm.get_conf("non_existent_configuration_file")
4551

4652

@@ -50,7 +56,7 @@ def test_conf_manager_when_conf_file_exists_but_no_specific_stanza_then_throw_ex
5056

5157
splunk_ta_addon_settings_conf_file = cfm.get_conf("splunk_ta_addon_settings")
5258

53-
with pytest.raises(conf_manager.ConfStanzaNotExistException):
59+
with pytest.raises(soln_exceptions.ConfStanzaNotExistException):
5460
splunk_ta_addon_settings_conf_file.get(
5561
"non_existent_stanza_under_existing_conf_file"
5662
)
@@ -60,6 +66,7 @@ def test_conf_manager_when_conf_file_exists_but_no_specific_stanza_then_throw_ex
6066
"stanza_name,expected_result",
6167
[
6268
("logging", True),
69+
("proxy", True),
6370
("non_existent_stanza_under_existing_conf_file", False),
6471
],
6572
)
@@ -109,7 +116,7 @@ def test_conf_manager_delete_non_existent_stanza_then_throw_exception():
109116

110117
splunk_ta_addon_settings_conf_file = cfm.get_conf("splunk_ta_addon_settings")
111118

112-
with pytest.raises(conf_manager.ConfStanzaNotExistException):
119+
with pytest.raises(soln_exceptions.ConfStanzaNotExistException):
113120
splunk_ta_addon_settings_conf_file.delete(
114121
"non_existent_stanza_under_existing_conf_file"
115122
)
@@ -164,3 +171,68 @@ def test_get_log_level_incorrect_log_level_field():
164171
)
165172

166173
assert expected_log_level == log_level
174+
175+
176+
def test_get_proxy_dict():
177+
session_key = context.get_session_key()
178+
expected_proxy_dict = VALID_PROXY_DICT
179+
proxy_dict = conf_manager.get_proxy_dict(
180+
logger=mock.MagicMock(),
181+
session_key=session_key,
182+
app_name="solnlib_demo",
183+
conf_name="splunk_ta_addon_settings",
184+
)
185+
assert expected_proxy_dict == proxy_dict
186+
187+
188+
def test_invalid_proxy_port():
189+
session_key = context.get_session_key()
190+
191+
with pytest.raises(soln_exceptions.InvalidPortError):
192+
conf_manager.get_proxy_dict(
193+
logger=mock.MagicMock(),
194+
session_key=session_key,
195+
app_name="solnlib_demo",
196+
conf_name="splunk_ta_addon_settings_invalid",
197+
proxy_stanza="invalid_proxy",
198+
proxy_port="proxy_port",
199+
)
200+
201+
202+
def test_invalid_proxy_host():
203+
session_key = context.get_session_key()
204+
205+
with pytest.raises(soln_exceptions.InvalidHostnameError):
206+
conf_manager.get_proxy_dict(
207+
logger=mock.MagicMock(),
208+
session_key=session_key,
209+
app_name="solnlib_demo",
210+
conf_name="splunk_ta_addon_settings_invalid",
211+
proxy_stanza="invalid_proxy",
212+
proxy_host="proxy_url",
213+
)
214+
215+
216+
def test_conf_manager_exception():
217+
session_key = context.get_session_key()
218+
219+
with pytest.raises(soln_exceptions.ConfManagerException):
220+
conf_manager.get_proxy_dict(
221+
logger=mock.MagicMock(),
222+
session_key=session_key,
223+
app_name="solnlib_demo",
224+
conf_name="splunk_ta_addon_settings_not_valid",
225+
)
226+
227+
228+
def test_conf_stanza_not_exist_exception():
229+
session_key = context.get_session_key()
230+
231+
with pytest.raises(soln_exceptions.ConfStanzaNotExistException):
232+
conf_manager.get_proxy_dict(
233+
logger=mock.MagicMock(),
234+
session_key=session_key,
235+
app_name="solnlib_demo",
236+
conf_name="splunk_ta_addon_settings",
237+
proxy_stanza="invalid_proxy",
238+
)

0 commit comments

Comments
 (0)