Service Discovery API call example fails with python and urllib

CMK version: 2.3.0p31.cre
OS version: AlmaLinux 8.10

Error message: When trying to start service discovery via the API: urllib.error.HTTPError: HTTP Error 302: The HTTP server returned a redirect error that would lead to an infinite loop

When attempting to run the example code for Python/urllib given in the API documents for service discovery of a single host using the API, I’m receiving the above error. It appears that urllib cannot handle infinite redirects. This issue seems to have been recognized in one of the other parts of the API - the “activate pending changes” endpoint has an option to pass "redirect": False in the request body, and that option is used in the urllib example (which works correctly).

Let me know if there are plans to add redirect as a body parameter to the service discovery API endpoint, or if there are other workarounds you know of. I have been investigating working around it on the urllib side by implementing a custom redirect handler. Regardless, the example shown in the API docs fails.

Thanks!
Mac

welcome

did you do “mode”: “refresh”?

Yes. Here’s the full script I used which generated that error: There is a slight difference from the example in that I’m bypassing TLS cert checking, but otherwise it is the example in the docs:

#!/usr/bin/env python3
import json
import pprint
import urllib.request
import ssl

HOST_NAME = "localhost"
SITE_NAME = "test"
PROTO = "https" #[http|https]
API_URL = f"{PROTO}://{HOST_NAME}/{SITE_NAME}/check_mk/api/1.0"

USERNAME = "cmkadmin"
PASSWORD = "[redacted]"

# don't care about localhost's cert
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE

request = urllib.request.Request(
    f"{API_URL}/domain-types/service_discovery_run/actions/start/invoke",
    method="POST",
    headers={
        "Authorization": f"Bearer {USERNAME} {PASSWORD}",
        "Accept": "application/json",
        "Content-Type": 'application/json',  # (required) A header specifying which type of content is in the request/response body.
    },
    data=json.dumps({"host_name": "[redacted]", "mode": "refresh"}).encode('utf-8'),
)
# Will raise an HTTPError if status code is >= 400
resp = urllib.request.urlopen(request, context=ctx)
if resp.status == 200:
    pprint.pprint(json.loads(resp.read().decode()))
elif resp.status == 302:
    print('Redirected to', resp.headers['location'])

Here’s the full error traceback:

Traceback (most recent call last):
  File "/root/./example-redirect-fail.py", line 31, in <module>
    resp = urllib.request.urlopen(request, context=ctx)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib64/python3.12/urllib/request.py", line 215, in urlopen
    return opener.open(url, data, timeout)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib64/python3.12/urllib/request.py", line 521, in open
    response = meth(req, response)
               ^^^^^^^^^^^^^^^^^^^
  File "/usr/lib64/python3.12/urllib/request.py", line 630, in http_response
    response = self.parent.error(
               ^^^^^^^^^^^^^^^^^^
  File "/usr/lib64/python3.12/urllib/request.py", line 553, in error
    result = self._call_chain(*args)
             ^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib64/python3.12/urllib/request.py", line 492, in _call_chain
    result = func(*args)
             ^^^^^^^^^^^
  File "/usr/lib64/python3.12/urllib/request.py", line 745, in http_error_302
    return self.parent.open(new, timeout=req.timeout)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib64/python3.12/urllib/request.py", line 521, in open
    response = meth(req, response)
               ^^^^^^^^^^^^^^^^^^^
  File "/usr/lib64/python3.12/urllib/request.py", line 630, in http_response
    response = self.parent.error(
               ^^^^^^^^^^^^^^^^^^
  File "/usr/lib64/python3.12/urllib/request.py", line 553, in error
    result = self._call_chain(*args)
             ^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib64/python3.12/urllib/request.py", line 492, in _call_chain
    result = func(*args)
             ^^^^^^^^^^^
  File "/usr/lib64/python3.12/urllib/request.py", line 745, in http_error_302
    return self.parent.open(new, timeout=req.timeout)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib64/python3.12/urllib/request.py", line 521, in open
    response = meth(req, response)
               ^^^^^^^^^^^^^^^^^^^
  File "/usr/lib64/python3.12/urllib/request.py", line 630, in http_response
    response = self.parent.error(
               ^^^^^^^^^^^^^^^^^^
  File "/usr/lib64/python3.12/urllib/request.py", line 553, in error
    result = self._call_chain(*args)
             ^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib64/python3.12/urllib/request.py", line 492, in _call_chain
    result = func(*args)
             ^^^^^^^^^^^
  File "/usr/lib64/python3.12/urllib/request.py", line 745, in http_error_302
    return self.parent.open(new, timeout=req.timeout)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib64/python3.12/urllib/request.py", line 521, in open
    response = meth(req, response)
               ^^^^^^^^^^^^^^^^^^^
  File "/usr/lib64/python3.12/urllib/request.py", line 630, in http_response
    response = self.parent.error(
               ^^^^^^^^^^^^^^^^^^
  File "/usr/lib64/python3.12/urllib/request.py", line 553, in error
    result = self._call_chain(*args)
             ^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib64/python3.12/urllib/request.py", line 492, in _call_chain
    result = func(*args)
             ^^^^^^^^^^^
  File "/usr/lib64/python3.12/urllib/request.py", line 745, in http_error_302
    return self.parent.open(new, timeout=req.timeout)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib64/python3.12/urllib/request.py", line 521, in open
    response = meth(req, response)
               ^^^^^^^^^^^^^^^^^^^
  File "/usr/lib64/python3.12/urllib/request.py", line 630, in http_response
    response = self.parent.error(
               ^^^^^^^^^^^^^^^^^^
  File "/usr/lib64/python3.12/urllib/request.py", line 553, in error
    result = self._call_chain(*args)
             ^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib64/python3.12/urllib/request.py", line 492, in _call_chain
    result = func(*args)
             ^^^^^^^^^^^
  File "/usr/lib64/python3.12/urllib/request.py", line 734, in http_error_302
    raise HTTPError(req.full_url, code,
urllib.error.HTTPError: HTTP Error 302: The HTTP server returned a redirect error that would lead to an infinite loop.
The last 30x error message was:
FOUND

Thanks!
Mac

Then this is expected behavior. Tabular rasa will work the same.
Its quite well explained in the docs.
It should retorn a 303 however so not sure about this particular error

I understand the expected behavior for the endpoint is to create an infinite redirect to ensure blocking, sure.

Is the expected behavior of the urllib example given in the docs is to fail with the above error?

Should the example, perhaps, consist of sequential calls to new, remove, and fix_all, to get the same behavior as refresh but without the redirect?