nolar / kopf

A Python framework to write Kubernetes operators in just a few lines of code
https://kopf.readthedocs.io/
MIT License
2.09k stars 158 forks source link

Allowing passing of 'status' to TemporaryError, PermanentError when raised. #693

Open GrahamDumpleton opened 3 years ago

GrahamDumpleton commented 3 years ago

Problem

The return value from a handler when successful is allowed to be a dictionary consisting of status values. This dictionary of values will be added to the status area of the Kubernetes custom resource. As Kopf also keeps status in the custom resource, the status generated by a user would be placed under a sub key of the status where the name defaulted to being the name of the handler, or the value of the id option given to the decorator applied to the handler.

@kopf.on.create("example.com", "v1", "myresource", id="myapp")
def workshop_session_create(name, meta, spec, **_):
    ...

    return {"phase": "Starting"}

The custom resource would be set to include:

status:
  myapp:
    phase: Starting

One could then use a print column in the CRD to show this value when using kubectl get.

      - name: Status
        type: string
        priority: 0
        description: The status of the workshop session.
        jsonPath: .status.myapp.phase

In the case where an exception is raised from the handler using TemporaryError or PermanentError, since there is no traditional return value, one cannot return a status in the same way. Instead, what one has to do is accept the patch argument to the handler and add values in that mapping to have them set.

@kopf.on.create("example.com", "v1", "myresource", id="myapp")
def workshop_session_create(name, meta, patch, spec, **_):
    ...

    if error:
        patch["status"] = {"myapp": {"phase": "Pending"}}
        raise kopf.TemporaryError("message")

    return {"phase": "Starting"}

Although this allows status to be set when errors are raised, it is clumsy as you need to manually ensure you specify the sub key with same id, ie., name of handler function, or id argument to decorator.

This method also means that if handler calls other functions that one needs to pass the patch argument down to called sub functions that may generate the Kopf TemporaryError or PermanentError exceptions where one may want to set a more specific status which could help to indicate that an error occurred and what the solution may be.

Any sub functions would also need to be passed the value of id to use since they may be generic functions which are used by multiple handlers, which may use default generated id or different manually provided values.

Proposal

Have any Kopf exceptions which can be raised from a handler accept a status argument. This would accept what otherwise could be returned from a handler in a successful case. That is, dictionary of values for status, but without the need to manually specify any id for sub key.

@kopf.on.create("example.com", "v1", "myresource", id="myapp")
def workshop_session_create(name, meta, spec, **_):
    ...

    if error:
        raise kopf.TemporaryError("message", status={"phase": "Pending"})

    return {"phase": "Starting"}

Kopf framework can catch these exception types and add the status to the patch for setting.

    try:
        # call the handler
    except kopf.TemporaryError as e:
        patch.setdefault("status", {}).setdefault(handler_id, {}).update(e.status)

This obviously needs to be generalised and catch appropriate base class etc.

Result would then be:

status:
  myapp:
    phase: Pending

Allowing this means that it wouldn't be necessary to manually construct patch, or pass patch object and handler ID into sub functions called by a handler and avoid that complexity on the user.

dheeg commented 11 months ago

@nolar It would be really great to have this feature in place