Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "sap-cloud-sdk"
version = "0.26.1"
version = "0.26.2"
Comment thread
betinacosta marked this conversation as resolved.
Outdated
description = "SAP Cloud SDK for Python"
readme = "README.md"
license = "Apache-2.0"
Expand Down
143 changes: 134 additions & 9 deletions src/sap_cloud_sdk/core/auditlog_ng/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
The create_client() function accepts an AuditLogNGConfig and returns a
ready-to-use AuditClient.

Usage:
Usage — explicit config::
Comment thread
NicoleMGomes marked this conversation as resolved.
Outdated

from sap_cloud_sdk.core.auditlog_ng import create_client, AuditLogNGConfig

config = AuditLogNGConfig(
Expand All @@ -18,6 +19,16 @@
)
client = create_client(config=config)

Usage — resolve from a Destination::

from sap_cloud_sdk.core.auditlog_ng import create_client

client = create_client(
destination_name="my-audit-destination",
destination_instance="my-binding-instance",
Comment thread
betinacosta marked this conversation as resolved.
fragment_name="prod-fragment", # optional
)

# Send an audit event (protobuf message)
event_id = client.send(event, "DataAccess")
client.close()
Expand All @@ -42,10 +53,92 @@
record_error_metric as _record_error_metric,
)

_DESTINATION_PROP_DEPLOYMENT_ID = "deploymentId"
_DESTINATION_PROP_DEPLOYMENT_REGION = "deploymentRegion"
_DESTINATION_PROP_NAMESPACE = "namespace"


def _get_config_from_destination(
destination_name: str,
destination_instance: str,
fragment_name: Optional[str] = None,
) -> dict[str, str]:
"""Resolve endpoint, deployment_id and namespace from a named Destination.

The destination must expose these custom properties:

- ``deploymentId`` (or ``deploymentRegion`` as fallback when absent/empty)
- ``namespace``

The destination ``url`` is used as the OTLP gRPC endpoint.
The lookup is always performed at ``ConsumptionLevel.SUBACCOUNT``.

Args:
destination_name: Name of the destination to resolve.
destination_instance: Destination service binding instance name,
passed as ``instance=`` to ``destination.create_client()``.
fragment_name: Optional fragment name merged into the destination
before resolution. Wrapped in ``ConsumptionOptions`` when provided.

Returns:
dict with keys ``endpoint``, ``deployment_id``, ``namespace``.

Raises:
ValueError: If the destination is not found or required properties
are missing.
"""
# Lazy import — keeps destination an optional dependency; importing auditlog_ng
# in environments without the destination package continues to work.
from sap_cloud_sdk.destination import (
ConsumptionOptions,
ConsumptionLevel,
create_client as _dest_create_client,
)

dest_client = _dest_create_client(instance=destination_instance)
options = ConsumptionOptions(fragment_name=fragment_name) if fragment_name else None
Comment thread
NicoleMGomes marked this conversation as resolved.
Outdated
destination = dest_client.get_destination(
name=destination_name, options=options, level=ConsumptionLevel.SUBACCOUNT
)

if destination is None:
raise ValueError(f"Destination '{destination_name}' was not found")

endpoint = destination.url
props = destination.properties

deployment_id = props.get(_DESTINATION_PROP_DEPLOYMENT_ID) or ""
Comment thread
betinacosta marked this conversation as resolved.
Outdated
if not deployment_id:
deployment_id = props.get(_DESTINATION_PROP_DEPLOYMENT_REGION) or ""
if not deployment_id:
raise ValueError(
f"Destination '{destination_name}' must provide either the "
f"'{_DESTINATION_PROP_DEPLOYMENT_ID}' or "
f"'{_DESTINATION_PROP_DEPLOYMENT_REGION}' property"
)

namespace = props.get(_DESTINATION_PROP_NAMESPACE) or ""
if not namespace:
raise ValueError(
f"Destination '{destination_name}' must provide the "
f"'{_DESTINATION_PROP_NAMESPACE}' property"
)

return {
"endpoint": endpoint,
"deployment_id": deployment_id,
"namespace": namespace,
}


def create_client(
*,
config: Optional[AuditLogNGConfig] = None,
# Destination-based resolution
destination_name: Optional[str] = None,
destination_instance: Optional[str] = None,
fragment_name: Optional[str] = None,
# Explicit connection parameters
endpoint: Optional[str] = None,
deployment_id: Optional[str] = None,
namespace: Optional[str] = None,
Expand All @@ -61,13 +154,33 @@ def create_client(
) -> AuditClient:
"""Create an AuditClient for sending audit events over OTLP/gRPC.

Either pass a pre-built ``config`` **or** the individual keyword arguments.
When ``config`` is provided the remaining keyword arguments are ignored.
Three mutually exclusive ways to provide configuration (evaluated in order):

1. **Explicit config object** — pass a pre-built :class:`AuditLogNGConfig`
via ``config``; all other keyword arguments are ignored.

2. **Destination-based resolution** — pass ``destination_name`` and
``destination_instance`` (both required); ``fragment_name`` is optional.
The Destination module resolves the named destination at subaccount level
and extracts ``endpoint``, ``deployment_id`` (with fallback to
``deploymentRegion``), and ``namespace`` from its properties.
The remaining keyword arguments (``cert_file``, ``key_file``, ``ca_file``,
``insecure``, ``service_name``, ``batch``, ``compression``, ``schema_url``)
are still forwarded to the resulting :class:`AuditLogNGConfig`.
Comment thread
cassiofariasmachado marked this conversation as resolved.
Outdated

3. **Explicit keyword arguments** — pass ``endpoint``, ``deployment_id``,
and ``namespace`` directly.

Args:
_telemetry_source: Internal parameter for telemetry. Not for external use.
config: Optional explicit configuration. If provided, all other
keyword arguments are ignored.
keyword arguments are ignored.
destination_name: Name of the SAP Destination to resolve. Must be
combined with ``destination_instance`` to enter the destination path.
destination_instance: Destination service binding instance name, passed
as ``instance=`` to ``destination.create_client()``. Must be combined
with ``destination_name`` to enter the destination path.
fragment_name: Optional destination fragment name merged before resolution.
endpoint: OTLP gRPC endpoint (``host:port``).
deployment_id: Deployment identifier.
namespace: Namespace identifier.
Expand All @@ -85,16 +198,28 @@ def create_client(

Raises:
ClientCreationError: If client creation fails.
ValueError: If required parameters are missing.
ValueError: If required parameters are missing or destination
resolution fails.
"""
try:
if config is None:
try:
if not endpoint or not deployment_id or not namespace:
raise ValueError(
"endpoint, deployment_id, and namespace are required "
"when config is not provided"
if destination_name and destination_instance:
Comment thread
cassiofariasmachado marked this conversation as resolved.
Outdated
resolved = _get_config_from_destination(
destination_name=destination_name,
destination_instance=destination_instance,
fragment_name=fragment_name,
)
endpoint = resolved["endpoint"]
deployment_id = resolved["deployment_id"]
namespace = resolved["namespace"]
else:
if not endpoint or not deployment_id or not namespace:
raise ValueError(
"endpoint, deployment_id, and namespace are required "
"when config is not provided"
)

config = AuditLogNGConfig(
endpoint=endpoint,
deployment_id=deployment_id,
Expand Down
52 changes: 50 additions & 2 deletions src/sap_cloud_sdk/core/auditlog_ng/user-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ The Auditlog NG client sends audit log events as OpenTelemetry (OTLP) LogRecords
- **mTLS** (mutual TLS with client certificates)
- **Insecure** mode (local testing / no-auth)
- **Binary protobuf** and **JSON** serialization formats
- **Destination-based configuration** — resolve connection parameters automatically from a named SAP Destination (SPII-based deployments)

---

Expand All @@ -36,7 +37,31 @@ The client depends on generated protobuf classes.

## Configuration

All constructor parameters for `AuditClient`:
`create_client` supports three mutually exclusive ways to provide configuration, evaluated in this order:

1. **Explicit config object** — pass a pre-built `AuditLogNGConfig` via `config=`.
2. **Destination-based resolution** — pass `destination_name` and `destination_instance`; connection parameters are resolved from the named SAP Destination automatically.
3. **Explicit keyword arguments** — pass `endpoint`, `deployment_id`, and `namespace` directly.

### Destination-based configuration parameters

| Parameter | Type | Required | Description |
|------------------------|--------|----------|-------------|
| `destination_name` | `str` | ✅ Yes | Name of the SAP Destination to resolve. |
| `destination_instance` | `str` | ✅ Yes | Destination service binding instance name. |
| `fragment_name` | `str` | ❌ No | Destination fragment merged before resolution (for tenant-specific overrides). |

The destination must expose these custom properties:

| Property | Required | Description |
|--------------------|----------|-------------|
| `deploymentId` | ✅ Yes (or `deploymentRegion`) | Deployment identifier. Falls back to `deploymentRegion` when absent or empty. |
| `deploymentRegion` | ✅ Fallback | Used as `deployment_id` when `deploymentId` is missing or empty. |
| `namespace` | ✅ Yes | Audit log namespace (e.g. `sap.als`). |

The destination `url` is used as the OTLP gRPC endpoint. The lookup is always performed at subaccount level.
Comment thread
cassiofariasmachado marked this conversation as resolved.
Outdated

### Explicit configuration parameters for `AuditClient`:

| Parameter | Type | Required | Default | Description |
|-----------------|---------|----------|----------------|-------------------------------------------------------------------------------------------------------|
Expand Down Expand Up @@ -80,7 +105,30 @@ from sap_cloud_sdk.core.auditlog_ng.gen.sap.auditlog.auditevent.v2 import audite

### Step 2: Initialize the Client

**With mTLS (production):**
**From a Destination (SPII-based deployments):**

```python
client = create_client(
destination_name="my_destination",
destination_instance="my-destination-binding",
# fragment_name="prod-fragment", # optional: merge a tenant-specific fragment
)
```

The SDK resolves `endpoint`, `deployment_id`, and `namespace` from the destination automatically.
You can still pass connection options alongside the destination parameters:

```python
client = create_client(
destination_name="my_destination",
destination_instance="my-destination-binding",
fragment_name="prod-fragment",
service_name="my-agent",
batch=True,
)
```

**With mTLS (explicit configuration):**

```python
client = create_client(
Expand Down
Loading
Loading