Skip to content

Commit c6b2d94

Browse files
committed
feat(sdk): add version() API to all SDKs (Rust, Go, Python, JS)
Add Version RPC support so SDKs can detect the guest-agent version and determine supported capabilities. On OS < 0.5.7 (which lacks the Version RPC), the call will fail, indicating only secp256k1 is safe to use. This prevents silent misuse of ed25519 on old OS versions that would ignore the algorithm field and return secp256k1 keys instead.
1 parent 034a2cd commit c6b2d94

File tree

6 files changed

+86
-0
lines changed

6 files changed

+86
-0
lines changed

sdk/go/dstack/client.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,29 @@ func (c *DstackClient) Attest(ctx context.Context, reportData []byte) (*AttestRe
460460
return &AttestResponse{Attestation: attestation}, nil
461461
}
462462

463+
// Represents the response from a Version request.
464+
type VersionResponse struct {
465+
Version string `json:"version"`
466+
Rev string `json:"rev"`
467+
}
468+
469+
// Gets the guest-agent version.
470+
//
471+
// Returns the version on OS >= 0.5.7.
472+
// Returns an error on older OS versions that lack the Version RPC.
473+
func (c *DstackClient) GetVersion(ctx context.Context) (*VersionResponse, error) {
474+
data, err := c.sendRPCRequest(ctx, "/Version", map[string]interface{}{})
475+
if err != nil {
476+
return nil, err
477+
}
478+
479+
var response VersionResponse
480+
if err := json.Unmarshal(data, &response); err != nil {
481+
return nil, err
482+
}
483+
return &response, nil
484+
}
485+
463486
// Sends a request to get information about the CVM instance
464487
func (c *DstackClient) Info(ctx context.Context) (*InfoResponse, error) {
465488
data, err := c.sendRPCRequest(ctx, "/Info", map[string]interface{}{})

sdk/js/src/index.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,13 @@ export interface AttestResponse {
104104
attestation: Hex
105105
}
106106

107+
export interface VersionResponse {
108+
__name__: Readonly<'VersionResponse'>
109+
110+
version: string
111+
rev: string
112+
}
113+
107114
export function to_hex(data: string | Buffer | Uint8Array): string {
108115
if (typeof data === 'string') {
109116
return Buffer.from(data).toString('hex');
@@ -281,6 +288,20 @@ export class DstackClient<T extends TcbInfo = TcbInfoV05x> {
281288
})
282289
}
283290

291+
/**
292+
* Query the guest-agent version.
293+
*
294+
* Returns the version on OS >= 0.5.7.
295+
* Throws on older OS versions that lack the Version RPC.
296+
*/
297+
async version(): Promise<VersionResponse> {
298+
const result = await send_rpc_request<{ version: string, rev: string }>(this.endpoint, '/Version', '{}')
299+
return Object.freeze({
300+
...result,
301+
__name__: 'VersionResponse',
302+
})
303+
}
304+
284305
async isReachable(): Promise<boolean> {
285306
try {
286307
// Use info endpoint to test connectivity with 500ms timeout

sdk/python/src/dstack_sdk/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from .dstack_client import TappdClient
1616
from .dstack_client import TcbInfo
1717
from .dstack_client import VerifyResponse
18+
from .dstack_client import VersionResponse
1819
from .encrypt_env_vars import EnvVar
1920
from .encrypt_env_vars import encrypt_env_vars
2021
from .encrypt_env_vars import encrypt_env_vars_sync
@@ -38,6 +39,7 @@
3839
"InfoResponse",
3940
"TcbInfo",
4041
"EventLog",
42+
"VersionResponse",
4143
# Utility functions
4244
"encrypt_env_vars_sync",
4345
"encrypt_env_vars",

sdk/python/src/dstack_sdk/dstack_client.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,11 @@ class VerifyResponse(BaseModel):
199199
valid: bool
200200

201201

202+
class VersionResponse(BaseModel):
203+
version: str
204+
rev: str
205+
206+
202207
class EventLog(BaseModel):
203208
imr: int
204209
event_type: int
@@ -498,6 +503,15 @@ async def verify(
498503
result = await self._send_rpc_request("Verify", payload)
499504
return VerifyResponse(**result)
500505

506+
async def version(self) -> VersionResponse:
507+
"""Query the guest-agent version.
508+
509+
Returns the version on OS >= 0.5.7.
510+
Raises an error on older OS versions that lack the Version RPC.
511+
"""
512+
result = await self._send_rpc_request("Version", {})
513+
return VersionResponse(**result)
514+
501515
async def is_reachable(self) -> bool:
502516
"""Return True if the service responds to a quick health call."""
503517
try:
@@ -588,6 +602,11 @@ def verify(
588602
"""Verify a signature."""
589603
raise NotImplementedError
590604

605+
@call_async
606+
def version(self) -> VersionResponse:
607+
"""Query the guest-agent version."""
608+
raise NotImplementedError
609+
591610
@call_async
592611
def is_reachable(self) -> bool:
593612
"""Return True if the service responds to a quick health call."""

sdk/rust/src/dstack_client.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,16 @@ impl DstackClient {
171171
Ok(InfoResponse::validated_from_value(response)?)
172172
}
173173

174+
/// Query the guest-agent version.
175+
///
176+
/// Returns `Ok(VersionResponse)` on OS >= 0.5.7.
177+
/// Returns an error on older OS versions that lack the Version RPC.
178+
pub async fn version(&self) -> Result<VersionResponse> {
179+
let response = self.send_rpc_request("/Version", &json!({})).await?;
180+
let response = serde_json::from_value::<VersionResponse>(response)?;
181+
Ok(response)
182+
}
183+
174184
pub async fn emit_event(&self, event: String, payload: Vec<u8>) -> Result<()> {
175185
if event.is_empty() {
176186
anyhow::bail!("Event name cannot be empty")

sdk/rust/types/src/dstack.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,3 +278,14 @@ pub struct VerifyResponse {
278278
/// Whether the signature is valid
279279
pub valid: bool,
280280
}
281+
282+
/// Response from a Version request
283+
#[derive(Debug, Clone, Serialize, Deserialize)]
284+
#[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))]
285+
#[cfg_attr(feature = "borsh_schema", derive(BorshSchema))]
286+
pub struct VersionResponse {
287+
/// The dstack version (e.g. "0.5.7")
288+
pub version: String,
289+
/// Git revision
290+
pub rev: String,
291+
}

0 commit comments

Comments
 (0)