Skip to content

Commit 679db2b

Browse files
committed
feat(api): support managed custom-domain TLS
1 parent f4d80ba commit 679db2b

22 files changed

Lines changed: 730 additions & 89 deletions
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
Feature: Managed custom-domain TLS contract
2+
3+
@managed-tls @TLS-MANAGED-001
4+
Scenario: Managed TLS uses an explicit secret-free request and DNS lifecycle response
5+
Given a managed custom-domain TLS request
6+
When the client encodes the request and decodes a pending verification response
7+
Then the request selects managed TLS without certificate material
8+
And the response exposes the managed lifecycle and DNS records

‎features/steps/sdk_contract_steps.py‎

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,15 @@
1313
)
1414

1515
from volcano_sdk import VolcanoClient
16+
from volcano_sdk._generated.models.create_frontend_custom_domain_request import (
17+
CreateFrontendCustomDomainRequest,
18+
)
19+
from volcano_sdk._generated.models.frontend_custom_domain_response import (
20+
FrontendCustomDomainResponse,
21+
)
22+
from volcano_sdk._generated.models.managed_frontend_custom_domain_tls_config import (
23+
ManagedFrontendCustomDomainTLSConfig,
24+
)
1625

1726
ACCESS_TOKEN_CLOCK_TICK_SECONDS = 1.1
1827

@@ -395,3 +404,67 @@ def subscriber_received_message(context: Any) -> None:
395404
world = _world(context)
396405
assert world.last_outcome is not None
397406
assert world.last_outcome.value == world.realtime_message
407+
408+
409+
@given("a managed custom-domain TLS request")
410+
def managed_tls_request(context: Any) -> None:
411+
_world(context).managed_tls_request = CreateFrontendCustomDomainRequest(
412+
domain="app.example.com",
413+
tls=ManagedFrontendCustomDomainTLSConfig(mode="managed"),
414+
)
415+
416+
417+
@when("the client encodes the request and decodes a pending verification response")
418+
def encode_managed_tls_request(context: Any) -> None:
419+
world = _world(context)
420+
world.managed_tls_wire_request = world.managed_tls_request.to_dict()
421+
world.managed_tls_response = FrontendCustomDomainResponse.from_dict(
422+
{
423+
"domain": "app.example.com",
424+
"tls_mode": "managed",
425+
"domain_status": "pending_verification",
426+
"verification_status": "pending",
427+
"verification_records": [
428+
{
429+
"name": "_token.app.example.com",
430+
"type": "CNAME",
431+
"value": "_validation.volcano.dev",
432+
}
433+
],
434+
"required_routing_record": {
435+
"record_type": "CNAME",
436+
"name": "app.example.com",
437+
"value": "frontend.frontends.volcano.dev",
438+
},
439+
"effective_urls": ["https://frontend.frontends.volcano.dev/"],
440+
"created_at": "2026-09-02T12:00:00Z",
441+
"updated_at": "2026-09-02T12:00:00Z",
442+
}
443+
)
444+
445+
446+
@then("the request selects managed TLS without certificate material")
447+
def managed_tls_request_has_no_certificate(context: Any) -> None:
448+
assert _world(context).managed_tls_wire_request == {
449+
"domain": "app.example.com",
450+
"tls": {"mode": "managed"},
451+
}
452+
453+
454+
@then("the response exposes the managed lifecycle and DNS records")
455+
def managed_tls_response_has_lifecycle(context: Any) -> None:
456+
response = _world(context).managed_tls_response
457+
assert response.domain == "app.example.com"
458+
assert response.tls_mode == "managed"
459+
assert response.domain_status == "pending_verification"
460+
assert response.verification_status == "pending"
461+
assert response.verification_records[0].to_dict() == {
462+
"name": "_token.app.example.com",
463+
"type": "CNAME",
464+
"value": "_validation.volcano.dev",
465+
}
466+
assert response.required_routing_record.to_dict() == {
467+
"record_type": "CNAME",
468+
"name": "app.example.com",
469+
"value": "frontend.frontends.volcano.dev",
470+
}

‎openapi/openapi.yaml‎

Lines changed: 91 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10993,13 +10993,14 @@ components:
1099310993
- text_body
1099410994
CreateFrontendCustomDomainRequest:
1099510995
type: object
10996+
additionalProperties: false
1099610997
properties:
1099710998
domain:
1099810999
type: string
1099911000
description: Fully-qualified domain name (hostname only, no scheme/path)
1100011001
example: app.example.com
1100111002
tls:
11002-
$ref: '#/components/schemas/FrontendCustomDomainTLSConfig'
11003+
$ref: '#/components/schemas/CreateFrontendCustomDomainTLSConfig'
1100311004
required:
1100411005
- domain
1100511006
- tls
@@ -12492,6 +12493,7 @@ components:
1249212493
enum:
1249312494
- pending
1249412495
- verified
12496+
- failed
1249512497
verification_records:
1249612498
type: array
1249712499
items:
@@ -12516,15 +12518,35 @@ components:
1251612518
- effective_urls
1251712519
- created_at
1251812520
- updated_at
12519-
FrontendCustomDomainTLSConfig:
12521+
CreateFrontendCustomDomainTLSConfig:
12522+
oneOf:
12523+
- $ref: '#/components/schemas/ManagedFrontendCustomDomainTLSConfig'
12524+
- $ref: '#/components/schemas/BYOCFrontendCustomDomainTLSConfig'
12525+
discriminator:
12526+
propertyName: mode
12527+
mapping:
12528+
managed: '#/components/schemas/ManagedFrontendCustomDomainTLSConfig'
12529+
byoc: '#/components/schemas/BYOCFrontendCustomDomainTLSConfig'
12530+
ManagedFrontendCustomDomainTLSConfig:
12531+
type: object
12532+
description: Volcano issues and renews the certificate. Do not send certificate material.
12533+
additionalProperties: false
12534+
properties:
12535+
mode:
12536+
type: string
12537+
enum:
12538+
- managed
12539+
required:
12540+
- mode
12541+
BYOCFrontendCustomDomainTLSConfig:
1252012542
type: object
12543+
description: Use certificate material that you manage.
12544+
additionalProperties: false
1252112545
properties:
1252212546
mode:
1252312547
type: string
1252412548
enum:
1252512549
- byoc
12526-
default: byoc
12527-
description: BYOC is mandatory for custom domain creation.
1252812550
certificate_pem:
1252912551
type: string
1253012552
description: Required. PEM-encoded certificate.
@@ -12538,6 +12560,64 @@ components:
1253812560
- mode
1253912561
- certificate_pem
1254012562
- private_key_pem
12563+
FrontendCustomDomainTLSConfig:
12564+
type: object
12565+
deprecated: true
12566+
description: Deprecated compatibility model. Use BYOCFrontendCustomDomainTLSConfig.
12567+
properties:
12568+
mode:
12569+
type: string
12570+
enum:
12571+
- byoc
12572+
default: byoc
12573+
certificate_pem:
12574+
type: string
12575+
private_key_pem:
12576+
type: string
12577+
certificate_chain_pem:
12578+
type: string
12579+
required:
12580+
- mode
12581+
- certificate_pem
12582+
- private_key_pem
12583+
ProjectConfigFrontendCustomDomainTLSConfig:
12584+
oneOf:
12585+
- $ref: '#/components/schemas/ManagedProjectConfigFrontendCustomDomainTLSConfig'
12586+
- $ref: '#/components/schemas/BYOCProjectConfigFrontendCustomDomainTLSConfig'
12587+
discriminator:
12588+
propertyName: mode
12589+
mapping:
12590+
managed: '#/components/schemas/ManagedProjectConfigFrontendCustomDomainTLSConfig'
12591+
byoc: '#/components/schemas/BYOCProjectConfigFrontendCustomDomainTLSConfig'
12592+
ManagedProjectConfigFrontendCustomDomainTLSConfig:
12593+
type: object
12594+
additionalProperties: false
12595+
properties:
12596+
mode:
12597+
type: string
12598+
enum:
12599+
- managed
12600+
required:
12601+
- mode
12602+
BYOCProjectConfigFrontendCustomDomainTLSConfig:
12603+
type: object
12604+
additionalProperties: false
12605+
properties:
12606+
mode:
12607+
type: string
12608+
enum:
12609+
- byoc
12610+
certificate_pem:
12611+
type: string
12612+
description: PEM-encoded certificate for BYOC create or rotation. Omitted from exports.
12613+
private_key_pem:
12614+
type: string
12615+
description: PEM-encoded private key for BYOC create or rotation. Omitted from exports.
12616+
certificate_chain_pem:
12617+
type: string
12618+
description: Optional PEM-encoded certificate chain for BYOC. Omitted from exports.
12619+
required:
12620+
- mode
1254112621
FrontendDeployment:
1254212622
type: object
1254312623
properties:
@@ -12630,8 +12710,10 @@ components:
1263012710
properties:
1263112711
record_type:
1263212712
type: string
12713+
description: CNAME for a subdomain; ALIAS means provider-supported ALIAS, ANAME, or CNAME flattening at a zone apex.
1263312714
enum:
1263412715
- CNAME
12716+
- ALIAS
1263512717
name:
1263612718
type: string
1263712719
value:
@@ -12642,6 +12724,7 @@ components:
1264212724
- value
1264312725
FrontendDomainVerificationRecord:
1264412726
type: object
12727+
additionalProperties: false
1264512728
properties:
1264612729
name:
1264712730
type: string
@@ -14274,17 +14357,15 @@ components:
1427414357
type: object
1427514358
additionalProperties: false
1427614359
description: |
14277-
Custom domain with BYOC TLS (PRO plan). `tls` is required when the
14278-
domain is first created and optional afterwards: providing new TLS
14279-
material for the same domain rotates the certificate in place (zero
14280-
downtime); omitting `tls` keeps the stored certificate. TLS material is
14281-
write-only and omitted from config export.
14360+
Custom domain with managed or BYOC TLS (PRO plan). `tls` is required
14361+
when the domain is first created and optional afterwards. BYOC TLS
14362+
material is write-only and omitted from config export.
1428214363
properties:
1428314364
domain:
1428414365
type: string
1428514366
description: Fully-qualified domain name (hostname only, no scheme/path)
1428614367
tls:
14287-
$ref: '#/components/schemas/FrontendCustomDomainTLSConfig'
14368+
$ref: '#/components/schemas/ProjectConfigFrontendCustomDomainTLSConfig'
1428814369
required:
1428914370
- domain
1429014371
ProjectConfigDatabase:

‎src/volcano_sdk/_generated/models/__init__.py‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,10 @@
8383
from .batch_function_deploy_failure import BatchFunctionDeployFailure
8484
from .batch_function_deploy_failure_operation import BatchFunctionDeployFailureOperation
8585
from .batch_function_deploy_response import BatchFunctionDeployResponse
86+
from .byoc_frontend_custom_domain_tls_config import BYOCFrontendCustomDomainTLSConfig
87+
from .byoc_frontend_custom_domain_tls_config_mode import BYOCFrontendCustomDomainTLSConfigMode
88+
from .byoc_project_config_frontend_custom_domain_tls_config import BYOCProjectConfigFrontendCustomDomainTLSConfig
89+
from .byoc_project_config_frontend_custom_domain_tls_config_mode import BYOCProjectConfigFrontendCustomDomainTLSConfigMode
8690
from .call_o_auth_provider_api_body import CallOAuthProviderAPIBody
8791
from .call_o_auth_provider_api_body_body import CallOAuthProviderAPIBodyBody
8892
from .call_o_auth_provider_api_body_method import CallOAuthProviderAPIBodyMethod
@@ -266,6 +270,10 @@
266270
from .log_search_request import LogSearchRequest
267271
from .log_search_response import LogSearchResponse
268272
from .log_stream_request import LogStreamRequest
273+
from .managed_frontend_custom_domain_tls_config import ManagedFrontendCustomDomainTLSConfig
274+
from .managed_frontend_custom_domain_tls_config_mode import ManagedFrontendCustomDomainTLSConfigMode
275+
from .managed_project_config_frontend_custom_domain_tls_config import ManagedProjectConfigFrontendCustomDomainTLSConfig
276+
from .managed_project_config_frontend_custom_domain_tls_config_mode import ManagedProjectConfigFrontendCustomDomainTLSConfigMode
269277
from .metric_usage_data import MetricUsageData
270278
from .o_auth_config import OAuthConfig
271279
from .o_auth_config_provider import OAuthConfigProvider
@@ -536,6 +544,10 @@
536544
"BatchFunctionDeployFailure",
537545
"BatchFunctionDeployFailureOperation",
538546
"BatchFunctionDeployResponse",
547+
"BYOCFrontendCustomDomainTLSConfig",
548+
"BYOCFrontendCustomDomainTLSConfigMode",
549+
"BYOCProjectConfigFrontendCustomDomainTLSConfig",
550+
"BYOCProjectConfigFrontendCustomDomainTLSConfigMode",
539551
"CallOAuthProviderAPIBody",
540552
"CallOAuthProviderAPIBodyBody",
541553
"CallOAuthProviderAPIBodyMethod",
@@ -719,6 +731,10 @@
719731
"LogSearchRequest",
720732
"LogSearchResponse",
721733
"LogStreamRequest",
734+
"ManagedFrontendCustomDomainTLSConfig",
735+
"ManagedFrontendCustomDomainTLSConfigMode",
736+
"ManagedProjectConfigFrontendCustomDomainTLSConfig",
737+
"ManagedProjectConfigFrontendCustomDomainTLSConfigMode",
722738
"MetricUsageData",
723739
"OAuthConfig",
724740
"OAuthConfigProvider",
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
from __future__ import annotations
2+
3+
from collections.abc import Mapping
4+
from typing import Any, TypeVar, BinaryIO, TextIO, TYPE_CHECKING, Generator
5+
6+
from attrs import define as _attrs_define
7+
from attrs import field as _attrs_field
8+
9+
from ..types import UNSET, Unset
10+
11+
from ..models.byoc_frontend_custom_domain_tls_config_mode import BYOCFrontendCustomDomainTLSConfigMode
12+
from ..models.byoc_frontend_custom_domain_tls_config_mode import check_byoc_frontend_custom_domain_tls_config_mode
13+
from ..types import UNSET, Unset
14+
from typing import cast
15+
16+
17+
18+
19+
20+
21+
T = TypeVar("T", bound="BYOCFrontendCustomDomainTLSConfig")
22+
23+
24+
25+
@_attrs_define
26+
class BYOCFrontendCustomDomainTLSConfig:
27+
""" Use certificate material that you manage.
28+
29+
Attributes:
30+
mode (BYOCFrontendCustomDomainTLSConfigMode):
31+
certificate_pem (str): Required. PEM-encoded certificate.
32+
private_key_pem (str): Required. PEM-encoded private key.
33+
certificate_chain_pem (str | Unset): Optional PEM-encoded certificate chain.
34+
"""
35+
36+
mode: BYOCFrontendCustomDomainTLSConfigMode
37+
certificate_pem: str
38+
private_key_pem: str
39+
certificate_chain_pem: str | Unset = UNSET
40+
41+
42+
43+
44+
45+
def to_dict(self) -> dict[str, Any]:
46+
mode: str = self.mode
47+
48+
certificate_pem = self.certificate_pem
49+
50+
private_key_pem = self.private_key_pem
51+
52+
certificate_chain_pem = self.certificate_chain_pem
53+
54+
55+
field_dict: dict[str, Any] = {}
56+
57+
field_dict.update({
58+
"mode": mode,
59+
"certificate_pem": certificate_pem,
60+
"private_key_pem": private_key_pem,
61+
})
62+
if certificate_chain_pem is not UNSET:
63+
field_dict["certificate_chain_pem"] = certificate_chain_pem
64+
65+
return field_dict
66+
67+
68+
69+
@classmethod
70+
def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
71+
d = dict(src_dict)
72+
mode = check_byoc_frontend_custom_domain_tls_config_mode(d.pop("mode"))
73+
74+
75+
76+
77+
certificate_pem = d.pop("certificate_pem")
78+
79+
private_key_pem = d.pop("private_key_pem")
80+
81+
certificate_chain_pem = d.pop("certificate_chain_pem", UNSET)
82+
83+
byoc_frontend_custom_domain_tls_config = cls(
84+
mode=mode,
85+
certificate_pem=certificate_pem,
86+
private_key_pem=private_key_pem,
87+
certificate_chain_pem=certificate_chain_pem,
88+
)
89+
90+
return byoc_frontend_custom_domain_tls_config
91+

0 commit comments

Comments
 (0)