Skip to content

Commit 3f48501

Browse files
adamweeksAdam Weeks
andauthored
feat: manage BYODS datasource lifecycle (#35)
* feat(byods): manage datasource lifecycle * docs(byods): explain automatic datasource setup --------- Co-authored-by: Adam Weeks <adweeks+cisco@cisco.com>
1 parent e3831db commit 3f48501

18 files changed

Lines changed: 1153 additions & 28 deletions

AGENTS.MD

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ webex-byova-gateway-python/
8080
│ │ ├── i_vendor_connector.py
8181
│ │ └── local_audio_connector.py
8282
│ ├── core/ # Core gateway components
83+
│ │ ├── datasource_lifecycle.py
8384
│ │ ├── virtual_agent_router.py
8485
│ │ ├── wxcc_gateway_server.py
8586
│ │ └── *.py # Generated gRPC stubs
@@ -138,6 +139,15 @@ webex-byova-gateway-python/
138139
- Converts between gRPC and connector formats
139140
- Handles error conditions and conversation cleanup
140141

142+
### DataSourceLifecycle (`datasource_lifecycle.py`)
143+
144+
- Uses the `webex-byods-sdk` package rather than implementing BYODS REST calls
145+
- Discovers or registers the gateway datasource before gRPC starts accepting traffic
146+
- Reconciles URL, schema, audience, subject, and active status at startup
147+
- Renews the datasource JWS before `tokenExpiryTime` and retries transient failures
148+
- Reads credential values only from explicitly named environment variables
149+
- Requires an explicit datasource ID when discovery returns multiple matches
150+
141151
## Development Guidelines
142152

143153
### Educational and Reference Purpose

README.md

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ The sample includes:
2727

2828
- A BYOVA gRPC server with `ListVirtualAgents` and `ProcessCallerInput`
2929
- JWT validation for the WxCC data plane
30+
- Optional BYODS datasource registration and pre-expiry JWS renewal
3031
- A configuration-driven connector router
3132
- Local audio and AWS Lex connectors
3233
- gRPC and HTTP health checks
@@ -53,7 +54,8 @@ Before WxCC can connect to this gateway, you need:
5354
exchange domain for your gateway
5455
- Authorization of that Service App by an administrator in the target organization
5556
- A publicly reachable TLS-enabled gRPC server URL on the authorized domain
56-
- An `ACTIVE` BYOVA data-source registration whose URL exactly matches the public server URL
57+
- An `ACTIVE` BYOVA data-source registration whose URL exactly matches the public server URL;
58+
the gateway can create and renew this registration when datasource management is enabled
5759
- A Contact Center AI virtual-agent configuration and a test flow that uses the Virtual
5860
Agent V2 activity
5961
- A configured gateway connector: use the local audio connector for a vendor-neutral
@@ -107,6 +109,30 @@ Never use disabled authentication for a Webex-connected or production endpoint.
107109
end-to-end test, configure the exact registered datasource URL and keep JWT enforcement
108110
enabled.
109111
112+
### Configure Automatic Datasource Management
113+
114+
The optional datasource lifecycle uses
115+
[`webex-byods-sdk`](https://github.com/WebexCommunity/webex-python-byods-sdk) to discover or
116+
register this gateway before gRPC starts accepting traffic and to renew its JWS before
117+
expiry. Configure `jwt_validation.datasource_url`, then enable:
118+
119+
```yaml
120+
data_source:
121+
enabled: true
122+
auth:
123+
type: "oauth_refresh"
124+
client_id_env: "WEBEX_BYODS_CLIENT_ID"
125+
client_secret_env: "WEBEX_BYODS_CLIENT_SECRET"
126+
refresh_token_env: "WEBEX_BYODS_REFRESH_TOKEN"
127+
```
128+
129+
Set the named environment variables from an authorized Service App with
130+
`spark-admin:datasource_read` and `spark-admin:datasource_write`. The gateway discovers an
131+
exact URL/schema/audience/subject match when no datasource ID is configured, reconciles
132+
configuration drift, and renews the token 60 minutes before expiry by default. See the
133+
[Configuration Reference](config/README.md#byods-datasource-lifecycle) for all settings and
134+
the static-token development option.
135+
110136
### Run
111137

112138
```bash
@@ -186,7 +212,7 @@ webex-byova-gateway-python/
186212
├── src/
187213
│ ├── auth/ # gRPC JWT validation
188214
│ ├── connectors/ # Virtual-agent connectors
189-
│ ├── core/ # Gateway server, routing, and health
215+
│ ├── core/ # Datasource lifecycle, gateway server, routing, and health
190216
│ ├── generated/ # Locally generated gRPC modules
191217
│ ├── monitoring/ # Development monitoring interface
192218
│ └── utils/ # Audio utilities

config/README.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,44 @@ URL. The gateway will not start with an empty value.
157157
See [gRPC JWT Authentication](../docs/JWT_AUTHENTICATION.md) for claims, issuers, deployment
158158
modes, and troubleshooting.
159159

160+
## BYODS Datasource Lifecycle
161+
162+
The optional `data_source` section uses `webex-byods-sdk` to discover or register the
163+
gateway datasource before the gRPC listener starts and renew its JWS before expiry:
164+
165+
```yaml
166+
data_source:
167+
enabled: true
168+
fail_startup_on_error: true
169+
id: ""
170+
id_env: "WEBEX_BYODS_DATA_SOURCE_ID"
171+
# Empty values inherit the JWT validation URL and schema.
172+
url: ""
173+
schema_id: ""
174+
audience: "BYOVAGateway"
175+
subject: "callAudioData"
176+
token_lifetime_minutes: 1440
177+
renewal_lead_time_minutes: 60
178+
retry_interval_seconds: 60
179+
auth:
180+
type: "oauth_refresh"
181+
client_id_env: "WEBEX_BYODS_CLIENT_ID"
182+
client_secret_env: "WEBEX_BYODS_CLIENT_SECRET"
183+
refresh_token_env: "WEBEX_BYODS_REFRESH_TOKEN"
184+
```
185+
186+
Values ending in `_env` name environment variables; they do not contain credentials. The
187+
Service App needs `spark-admin:datasource_read` and `spark-admin:datasource_write`.
188+
189+
When neither `id` nor the variable named by `id_env` supplies an ID, startup searches for a
190+
matching URL, schema, audience, and subject. It registers only when no match exists and
191+
rejects ambiguous matches. Explicit `url` and `schema_id` values must match the corresponding
192+
`jwt_validation` settings.
193+
194+
For short-lived development, use `auth.type: "static"` with
195+
`access_token_env: "WEBEX_BYODS_ACCESS_TOKEN"`. Static access tokens cannot be refreshed;
196+
use OAuth refresh credentials for unattended operation.
197+
160198
## Logging
161199

162200
```yaml
@@ -222,6 +260,7 @@ Common checks:
222260

223261
- [Local development](../docs/LOCAL_DEVELOPMENT.md)
224262
- [Local audio configuration](../docs/LOCAL_AUDIO_CONFIGURATION.md)
263+
- [BYODS datasource lifecycle](#byods-datasource-lifecycle)
225264
- [JWT authentication](../docs/JWT_AUTHENTICATION.md)
226265
- [Testing](../docs/TESTING.md)
227266
- [Return to the project README](../README.md)

config/config.yaml

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,42 @@ jwt_validation:
109109
# Public keys are fetched from Webex identity broker and cached to improve performance
110110
cache_duration_minutes: 60
111111

112+
# BYODS datasource registration and token lifecycle
113+
data_source:
114+
# Enable to discover/register this gateway with Webex at startup and renew
115+
# its JWS before expiration.
116+
enabled: false
117+
118+
# Stop gateway startup if registration or initial renewal fails.
119+
fail_startup_on_error: true
120+
121+
# Optional existing datasource ID. When empty, the gateway discovers a
122+
# matching datasource or registers one. id_env takes precedence when id is empty.
123+
id: ""
124+
id_env: "WEBEX_BYODS_DATA_SOURCE_ID"
125+
126+
# Leave URL and schema empty to reuse the JWT validation values above.
127+
# If explicitly configured, they must exactly match the JWT values.
128+
url: ""
129+
schema_id: ""
130+
audience: "BYOVAGateway"
131+
subject: "callAudioData"
132+
133+
# Webex supports a maximum JWS lifetime of 1440 minutes (24 hours).
134+
token_lifetime_minutes: 1440
135+
renewal_lead_time_minutes: 60
136+
retry_interval_seconds: 60
137+
138+
# The SDK reads credential values from these environment variables. An OAuth
139+
# refresh token supports unattended access-token renewal. A static access
140+
# token can be used for short-lived development by setting type: "static"
141+
# and access_token_env: "WEBEX_BYODS_ACCESS_TOKEN".
142+
auth:
143+
type: "oauth_refresh"
144+
client_id_env: "WEBEX_BYODS_CLIENT_ID"
145+
client_secret_env: "WEBEX_BYODS_CLIENT_SECRET"
146+
refresh_token_env: "WEBEX_BYODS_REFRESH_TOKEN"
147+
112148
# Logging configuration
113149
logging:
114150
gateway:

docs/CUSTOMER_EVALUATION.md

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,16 +67,25 @@ a derivative of this sample.
6767
3. Decide who will own the Service App, public gateway endpoint, and voice-agent connector.
6868
4. Create and authorize the Service App, including the Voice Virtual Agent schema and the
6969
gateway's data exchange domain.
70-
5. Make the gateway available at a public TLS-enabled server URL, then register an `ACTIVE`
71-
BYOVA data source using that exact URL. Use the same value for
72-
`jwt_validation.datasource_url` in the gateway configuration.
70+
5. Make the gateway available at a public TLS-enabled server URL and set that exact value in
71+
`jwt_validation.datasource_url`. Enable automatic datasource lifecycle management to
72+
discover or register the `ACTIVE` BYOVA datasource and renew its JWS, or register and
73+
maintain it manually.
7374
6. Configure Contact Center AI and add the Virtual Agent V2 activity to a test flow.
7475

7576
Use the current [Service App authorization steps](https://help.webex.com/default/article/5g8s6u),
7677
[BYODS guide](https://developer.webex.com/webex-contact-center/docs/bring-your-own-data-source-cc),
7778
and [BYOVA developer guide](https://developer.webex.com/webex-contact-center/docs/bring-your-own-virtual-agent)
7879
for the Webex onboarding flow.
7980

81+
For the automatic path, provide the authorized Service App OAuth credentials through
82+
environment variables and set `data_source.enabled: true`. The gateway establishes the
83+
datasource before accepting gRPC traffic and prints the ID needed by the Contact Center
84+
virtual-agent feature. Follow
85+
[Local Audio Connector Configuration](LOCAL_AUDIO_CONFIGURATION.md#3-enable-automatic-byova-data-source-registration)
86+
for a complete sandbox example and
87+
[Configuration](../config/README.md#byods-datasource-lifecycle) for all lifecycle settings.
88+
8089
For a complete first-time walkthrough, including these dependencies in the required order,
8190
follow the
8291
[BYOVA with AWS Lex setup guide](https://developer.webex.com/webex-contact-center/docs/byova-and-aws-lex).

docs/JWT_AUTHENTICATION.md

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,39 @@ https://gateway.example.com:443
5656
Copy the registered value rather than reconstructing it. Use the same URL when a temporary
5757
development endpoint changes.
5858

59+
### Automatic Registration and JWT Claim Alignment
60+
61+
The optional `data_source` lifecycle can discover or register the datasource before the
62+
gRPC listener starts and renew its JWS before expiry. Leave its URL and schema empty to
63+
inherit the JWT values:
64+
65+
```yaml
66+
jwt_validation:
67+
enabled: true
68+
enforce_validation: true
69+
datasource_url: "https://your-gateway.example.com:443"
70+
datasource_schema_uuid: "5397013b-7920-4ffc-807c-e8a3e0a18f43"
71+
72+
data_source:
73+
enabled: true
74+
fail_startup_on_error: true
75+
url: ""
76+
schema_id: ""
77+
auth:
78+
type: "oauth_refresh"
79+
client_id_env: "WEBEX_BYODS_CLIENT_ID"
80+
client_secret_env: "WEBEX_BYODS_CLIENT_SECRET"
81+
refresh_token_env: "WEBEX_BYODS_REFRESH_TOKEN"
82+
```
83+
84+
Set the named environment variables from an authorized Service App. With empty lifecycle
85+
URL and schema values, registration and validation share one source of truth. If both
86+
sections explicitly configure either value, startup rejects a mismatch.
87+
88+
The datasource token managed by this lifecycle is separate from each inbound request JWT:
89+
the lifecycle creates and renews the registration's JWS, while the interceptor validates
90+
the signed JWT Webex sends with a gRPC request.
91+
5992
### Datasource Schema UUID
6093

6194
The standard Voice Virtual Agent schema UUID used by this sample is:
@@ -153,7 +186,8 @@ before making a key request to prevent arbitrary key-fetch URLs.
153186

154187
### Datasource Claims Validation Failed
155188

156-
- Copy the exact registered datasource URL into the configuration.
189+
- Copy the exact registered datasource URL into the configuration, or enable automatic
190+
lifecycle management so registration inherits the JWT URL and schema.
157191
- Check whether the registered URL includes `:443` or a trailing path.
158192
- Confirm the token schema UUID matches the configured Voice Virtual Agent schema.
159193

docs/LOCAL_AUDIO_CONFIGURATION.md

Lines changed: 72 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ For an end-to-end sandbox call, you also need:
4141
- A Webex Contact Center sandbox or organization with BYOVA enabled
4242
- Contact Center administrator access
4343
- A Webex Service App authorized by the sandbox organization
44-
- A BYODS data-source registration using the BYOVA schema
44+
- Service App credentials with `spark-admin:datasource_read` and
45+
`spark-admin:datasource_write` when using automatic datasource registration
4546
- A publicly reachable HTTPS endpoint that supports HTTP/2 gRPC and routes to
4647
gateway port `50051`
4748
- A test entry point and a published flow containing a Virtual Agent V2 activity
@@ -207,21 +208,70 @@ The `datasource_url` must exactly match the URL used in the BYODS registration,
207208
including scheme, hostname, path, and any explicitly supplied port. Restart the
208209
gateway after changing it.
209210

210-
### 3. Register the BYOVA data source
211+
### 3. Enable automatic BYOVA data-source registration
211212

212213
Follow the Webex
213214
[Bring Your Own Virtual Agent](https://developer.webex.com/webex-contact-center/docs/bring-your-own-virtual-agent)
214215
and
215216
[Bring Your Own Data Source](https://developer.webex.com/webex-contact-center/docs/bring-your-own-data-source-cc)
216-
guides to:
217+
guides to confirm BYOVA is enabled, create a Service App with the required
218+
data-source scopes and allowed gateway domain, and have a sandbox administrator
219+
authorize it.
217220

218-
1. Confirm BYOVA is enabled for the sandbox organization.
219-
2. Create a Service App with the required data-source scopes and add the gateway
220-
hostname as an allowed domain.
221-
3. Have a sandbox administrator authorize the Service App.
222-
4. Register a data source with the BYOVA schema UUID
223-
`5397013b-7920-4ffc-807c-e8a3e0a18f43` and the exact URL configured above.
224-
5. Save the returned data-source ID.
221+
Provide the authorized OAuth credentials through environment variables:
222+
223+
```bash
224+
export WEBEX_BYODS_CLIENT_ID="your-client-id"
225+
export WEBEX_BYODS_CLIENT_SECRET="your-client-secret"
226+
export WEBEX_BYODS_REFRESH_TOKEN="your-refresh-token"
227+
```
228+
229+
Do not commit these values. Use your deployment platform's secret manager for
230+
shared or long-running environments.
231+
232+
Enable lifecycle management in `config/config.yaml`:
233+
234+
```yaml
235+
data_source:
236+
enabled: true
237+
fail_startup_on_error: true
238+
id: ""
239+
id_env: "WEBEX_BYODS_DATA_SOURCE_ID"
240+
# Empty values inherit jwt_validation.datasource_url and schema.
241+
url: ""
242+
schema_id: ""
243+
audience: "BYOVAGateway"
244+
subject: "callAudioData"
245+
token_lifetime_minutes: 1440
246+
renewal_lead_time_minutes: 60
247+
retry_interval_seconds: 60
248+
auth:
249+
type: "oauth_refresh"
250+
client_id_env: "WEBEX_BYODS_CLIENT_ID"
251+
client_secret_env: "WEBEX_BYODS_CLIENT_SECRET"
252+
refresh_token_env: "WEBEX_BYODS_REFRESH_TOKEN"
253+
```
254+
255+
Start the gateway after the public endpoint is available. Before accepting gRPC
256+
traffic, it searches for an exact URL, schema, audience, and subject match. It
257+
reuses and reconciles one match or registers a new datasource when no match
258+
exists. The startup summary prints the datasource ID and JWS expiry:
259+
260+
```text
261+
BYODS Datasource:
262+
• Management: ENABLED
263+
• ID: <data-source-id>
264+
• Token Expires: <timestamp>
265+
```
266+
267+
Keep the gateway running. It renews the JWS 60 minutes before expiry by default
268+
and retries a failed renewal every 60 seconds. If more than one existing
269+
datasource matches, set `WEBEX_BYODS_DATA_SOURCE_ID` to the intended ID and
270+
restart.
271+
272+
For a one-time manual registration instead, leave `data_source.enabled` set to
273+
`false`, register the same URL and BYOVA schema through the Webex API, and save
274+
the returned datasource ID. Automatic renewal is disabled in that mode.
225275

226276
No vendor credentials are needed for the local connector.
227277

@@ -231,7 +281,7 @@ In Control Hub:
231281

232282
1. Go to **Contact Center > Integrations > Features**.
233283
2. Create a virtual-agent feature using the authorized Service App.
234-
3. Use the data-source ID as the resource identifier.
284+
3. Use the datasource ID printed by gateway startup as the resource identifier.
235285
4. Give it a recognizable name such as `BYOVA Local Audio Test`.
236286

237287
### 5. Configure and publish the flow
@@ -297,6 +347,17 @@ JWT validation is enabled without a URL. For a localhost-only smoke test, set
297347
`jwt_validation.enabled` to `false`. For a sandbox call, configure the exact
298348
public BYODS URL and leave validation enabled.
299349

350+
### Automatic datasource registration fails during startup
351+
352+
- Confirm the three `WEBEX_BYODS_*` environment variables are available to the
353+
gateway process.
354+
- Confirm the authorized Service App has both datasource read and write scopes.
355+
- Confirm the public URL uses a domain allowed by the Service App.
356+
- If the logs report multiple matches, set `WEBEX_BYODS_DATA_SOURCE_ID` to the
357+
intended datasource ID.
358+
- Keep `fail_startup_on_error: true` for end-to-end testing so the gateway does
359+
not accept traffic without a managed datasource.
360+
300361
### The local agent does not appear in Flow Designer
301362

302363
- Confirm `/api/config` lists `Local Audio: Local Playback`.

0 commit comments

Comments
 (0)