Repository: splunk/addonfactory-solutions-library-python
Pull request #427: feat: improve information in the user-agent param
Author: login: sgoral-splunk
Labels: name: released
name: released on @beta
Created: 2025-03-04T13:22:16Z | Merged: 2025-03-06T13:02:21Z

**Issue number:** [ADDON-76041](https://splunk.atlassian.net/browse/ADDON-76041)
### PR Type

**What kind of change does this PR introduce?**
* [ ] Feature
* [ ] Bug Fix
* [x] Refactoring (no functional or API changes)
* [ ] Documentation Update
* [ ] Maintenance (dependency updates, CI, etc.)

## Summary

### Changes

Changed "User-Agent" header parameter from `Curl` to `solnlib/<version> rest-client <os platform>`

### User experience

The user will see more detailed "User-Agent" information in the logs regarding requests made using the rest client.

## Checklist

If an item doesn't apply to your changes, leave it unchecked.

* [x] I have performed a self-review of this change according to the [development guidelines](https://splunk.github.io/addonfactory-ucc-generator/contributing/#development-guidelines)
* [x] Tests have been added/modified to cover the changes [(testing doc)](https://splunk.github.io/addonfactory-ucc-generator/contributing/#build-and-test)
* [ ] Changes are documented
* [x] PR title and description follows the [contributing principles](https://splunk.github.io/addonfactory-ucc-generator/contributing/#pull-requests)



Diff:
diff --git a/solnlib/splunk_rest_client.py b/solnlib/splunk_rest_client.py
index f06eb3c5..7e858248 100644
--- a/solnlib/splunk_rest_client.py
+++ b/solnlib/splunk_rest_client.py
@@ -23,11 +23,13 @@
 
 import logging
 import os
+import sys
 import traceback
+import solnlib
+
 from io import BytesIO
 from urllib.parse import quote
 from urllib3.util.retry import Retry
-
 from splunklib import binding, client
 
 from .net_utils import validate_scheme_host_port
@@ -134,7 +136,7 @@ def request(url, message, **kwargs):
 
         body = message.get("body")
         headers = {
-            "User-Agent": "curl",
+            "User-Agent": f"solnlib/{solnlib.__version__} rest-client {sys.platform}",
             "Accept": "*/*",
             "Connection": "Keep-Alive",
         }
diff --git a/tests/integration/test_splunk_rest_client.py b/tests/integration/test_splunk_rest_client.py
new file mode 100644
index 00000000..557f7145
--- /dev/null
+++ b/tests/integration/test_splunk_rest_client.py
@@ -0,0 +1,49 @@
+#
+# Copyright 2025 Splunk Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import context
+import pytest
+import solnlib
+from time import sleep
+from splunklib import binding
+from solnlib import splunk_rest_client as rest_client
+
+from _search import search
+
+
+def test_rest_client_user_agent():
+    test_url = r'search index = _internal uri_path="*/servicesNS/nobody/test_app/some/unexisting/url"'
+    user_agent = f"solnlib/{solnlib.__version__} rest-client linux"
+    session_key = context.get_session_key()
+    wrong_url = r"some/unexisting/url"
+    rc = rest_client.SplunkRestClient(
+        session_key,
+        app="test_app",
+        owner=context.owner,
+        scheme=context.scheme,
+        host=context.host,
+        port=context.port,
+    )
+    with pytest.raises(binding.HTTPError):
+        rc.get(wrong_url)
+
+    for i in range(50):
+        search_results = search(session_key, test_url)
+        if len(search_results) > 0:
+            break
+        sleep(0.5)
+
+    assert user_agent in search_results[0]["_raw"]


Reviews:
user: login: kkedziak-splunk
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T13:38:07Z
user: login: artemrys
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-04T13:44:49Z
user: login: sgoral-splunk
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T15:10:31Z
user: login: kkedziak-splunk
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T16:01:58Z
user: login: hetangmodi-crest
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-05T10:03:45Z
user: login: sgoral-splunk
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-05T13:47:51Z
user: login: artemrys
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-06T11:26:43Z

Review comments:
diff_hunk: @@ -0,0 +1,45 @@
+#
+# Copyright 2025 Splunk Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import context
+import pytest
+import solnlib
+import sys
+from splunklib import binding
+from solnlib import splunk_rest_client as rest_client
+
+from _search import search
+
+
+def test_rest_client_user_agent():
+    test_url = r'search index = _internal uri_path="*/servicesNS/nobody/test_app/some/unexisting/url"'
+    user_agent = f"solnlib/{solnlib.__version__} rest-client {sys.platform}"
path: tests/integration/test_splunk_rest_client.py
user: login: artemrys
body: Can we please put a real value for `sys.platform`?
created_at: 2025-03-04T13:44:49Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,45 @@
+#
+# Copyright 2025 Splunk Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import context
+import pytest
+import solnlib
+import sys
+from splunklib import binding
+from solnlib import splunk_rest_client as rest_client
+
+from _search import search
+
+
+def test_rest_client_user_agent():
+    test_url = r'search index = _internal uri_path="*/servicesNS/nobody/test_app/some/unexisting/url"'
+    user_agent = f"solnlib/{solnlib.__version__} rest-client {sys.platform}"
path: tests/integration/test_splunk_rest_client.py
user: login: sgoral-splunk
body: done
created_at: 2025-03-04T15:10:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,49 @@
+#
+# Copyright 2025 Splunk Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import context
+import pytest
+import solnlib
+from time import sleep
+from splunklib import binding
+from solnlib import splunk_rest_client as rest_client
+
+from _search import search
+
+
+def test_rest_client_user_agent():
+    test_url = r'search index = _internal uri_path="*/servicesNS/nobody/test_app/some/unexisting/url"'
+    user_agent = f"solnlib/{solnlib.__version__} rest-client linux"
+    session_key = context.get_session_key()
+    wrong_url = r"some/unexisting/url"
+    rc = rest_client.SplunkRestClient(
+        session_key,
+        app="test_app",
+        owner=context.owner,
+        scheme=context.scheme,
+        host=context.host,
+        port=context.port,
+    )
+    with pytest.raises(binding.HTTPError):
+        rc.get(wrong_url)
+
+    for i in range(50):
+        search_results = search(session_key, test_url)
+        if len(search_results) > 0:
+            break
+        sleep(0.5)
path: tests/integration/test_splunk_rest_client.py
user: login: hetangmodi-crest
body: Do we require this sleep as it is already present in "search" import?








created_at: 2025-03-05T10:03:45Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,49 @@
+#
+# Copyright 2025 Splunk Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import context
+import pytest
+import solnlib
+from time import sleep
+from splunklib import binding
+from solnlib import splunk_rest_client as rest_client
+
+from _search import search
+
+
+def test_rest_client_user_agent():
+    test_url = r'search index = _internal uri_path="*/servicesNS/nobody/test_app/some/unexisting/url"'
+    user_agent = f"solnlib/{solnlib.__version__} rest-client linux"
+    session_key = context.get_session_key()
+    wrong_url = r"some/unexisting/url"
+    rc = rest_client.SplunkRestClient(
+        session_key,
+        app="test_app",
+        owner=context.owner,
+        scheme=context.scheme,
+        host=context.host,
+        port=context.port,
+    )
+    with pytest.raises(binding.HTTPError):
+        rc.get(wrong_url)
+
+    for i in range(50):
+        search_results = search(session_key, test_url)
+        if len(search_results) > 0:
+            break
+        sleep(0.5)
path: tests/integration/test_splunk_rest_client.py
user: login: sgoral-splunk
body: you are right, this sleep here is not that relevant but since we break after result is found I think we can keep it.
created_at: 2025-03-05T13:47:50Z
author_association: CONTRIBUTOR

PR comments:
user: login: artemrys
created_at: 2025-03-04T16:02:23Z
body: I'd like to get this one released and it seems like a very small but `feat`, @sgoral-splunk what do you think? :)
author_association: MEMBER
user: login: sgoral-splunk
created_at: 2025-03-05T13:48:28Z
body: > I'd like to get this one released and it seems like a very small but `feat`, @sgoral-splunk what do you think? :)

sure, lets release it :)
author_association: CONTRIBUTOR
user: login: srv-rr-github-token
created_at: 2025-03-06T13:08:17Z
body: :tada: This PR is included in version 6.2.0-beta.1 :tada:

The release is available on [GitHub release](https://github.com/splunk/addonfactory-solutions-library-python/releases/tag/v6.2.0-beta.1)

Your **[semantic-release](https://github.com/semantic-release/semantic-release)** bot :package::rocket:
author_association: CONTRIBUTOR
user: login: srv-rr-github-token
created_at: 2025-03-11T15:04:01Z
body: :tada: This PR is included in version 6.2.0 :tada:

The release is available on [GitHub release](https://github.com/splunk/addonfactory-solutions-library-python/releases/tag/v6.2.0)

Your **[semantic-release](https://github.com/semantic-release/semantic-release)** bot :package::rocket:
author_association: CONTRIBUTOR