Skip to content

Commit 0af413c

Browse files
committed
cvm: Refactor gateway key store and support legacy gateway fallback
- Merge get_or_generate_wg_keys and get_or_request_client_cert into get_or_generate_key_store that returns owned GatewayKeyStore - Reuse WireGuard keys when only cert expires - Generate two certs: one without quote (new gateways) and one with quote (legacy gateways) - Auto-detect legacy gateway by "no attestation provided" error and retry with quote cert - Add Version parsing utility in dstack-types for semver handling
1 parent 33209e2 commit 0af413c

5 files changed

Lines changed: 300 additions & 81 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dstack-types/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,7 @@ pub struct ImageInfo {
255255

256256
pub mod mr_config;
257257
pub mod shared_filenames;
258+
pub mod version;
258259

259260
/// Get the address of the dstack agent
260261
pub fn dstack_agent_address() -> String {

dstack-types/src/version.rs

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
// SPDX-FileCopyrightText: © 2025 Phala Network <dstack@phala.network>
2+
//
3+
// SPDX-License-Identifier: Apache-2.0
4+
5+
use std::cmp::Ordering;
6+
7+
/// Parsed semantic version with major, minor, and patch components.
8+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
9+
pub struct Version {
10+
pub major: u32,
11+
pub minor: u32,
12+
pub patch: u32,
13+
}
14+
15+
impl Version {
16+
/// Create a new version with the given components.
17+
pub const fn new(major: u32, minor: u32, patch: u32) -> Self {
18+
Self {
19+
major,
20+
minor,
21+
patch,
22+
}
23+
}
24+
25+
/// Parse a version string into a Version struct.
26+
///
27+
/// Handles various version formats:
28+
/// - Standard: "0.5.6"
29+
/// - Two segments: "0.5" (patch defaults to 0)
30+
/// - With extra parts: "0.5.6.1"
31+
/// - With prerelease: "0.5.6-alpha.0", "0.5.6-rc1"
32+
/// - With build metadata: "0.5.6+dcap.0"
33+
/// - Git describe format: "0.5.6-10-g1234abc"
34+
/// - Mixed: "0.5.6-alpha.0+dcap.0"
35+
///
36+
/// The prerelease and build metadata parts are truncated.
37+
/// Returns None if the version string is empty or cannot be parsed.
38+
pub fn parse(version: &str) -> Option<Self> {
39+
let version = version.trim();
40+
if version.is_empty() {
41+
return None;
42+
}
43+
44+
// Strip prerelease (-...) and build metadata (+...) suffixes
45+
// Find the first occurrence of '-' or '+'
46+
let version = version
47+
.split_once(|c| c == '-' || c == '+')
48+
.map(|(v, _)| v)
49+
.unwrap_or(version);
50+
51+
// Split by '.' and parse the first three components
52+
let mut parts = version.split('.');
53+
let major = parts.next()?.parse().ok()?;
54+
let minor = parts.next()?.parse().ok()?;
55+
// Patch is optional, defaults to 0
56+
let patch = parts.next().and_then(|s| s.parse().ok()).unwrap_or(0);
57+
// Ignore extra parts like "0.5.6.1"
58+
59+
Some(Self {
60+
major,
61+
minor,
62+
patch,
63+
})
64+
}
65+
}
66+
67+
impl PartialOrd for Version {
68+
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
69+
Some(self.cmp(other))
70+
}
71+
}
72+
73+
impl Ord for Version {
74+
fn cmp(&self, other: &Self) -> Ordering {
75+
match self.major.cmp(&other.major) {
76+
Ordering::Equal => match self.minor.cmp(&other.minor) {
77+
Ordering::Equal => self.patch.cmp(&other.patch),
78+
ord => ord,
79+
},
80+
ord => ord,
81+
}
82+
}
83+
}
84+
85+
impl std::fmt::Display for Version {
86+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
87+
write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
88+
}
89+
}
90+
91+
#[cfg(test)]
92+
mod tests {
93+
use super::*;
94+
95+
#[test]
96+
fn test_parse_standard() {
97+
let v = Version::parse("0.5.6").unwrap();
98+
assert_eq!(v, Version::new(0, 5, 6));
99+
}
100+
101+
#[test]
102+
fn test_parse_two_segments() {
103+
let v = Version::parse("0.5").unwrap();
104+
assert_eq!(v, Version::new(0, 5, 0));
105+
}
106+
107+
#[test]
108+
fn test_parse_with_extra_parts() {
109+
let v = Version::parse("0.5.6.1").unwrap();
110+
assert_eq!(v, Version::new(0, 5, 6));
111+
}
112+
113+
#[test]
114+
fn test_parse_with_prerelease() {
115+
assert_eq!(
116+
Version::parse("0.5.6-alpha.0").unwrap(),
117+
Version::new(0, 5, 6)
118+
);
119+
assert_eq!(Version::parse("0.5.6-rc1").unwrap(), Version::new(0, 5, 6));
120+
assert_eq!(
121+
Version::parse("0.5.6-beta2").unwrap(),
122+
Version::new(0, 5, 6)
123+
);
124+
}
125+
126+
#[test]
127+
fn test_parse_git_describe() {
128+
// git describe format: tag-commits-ghash
129+
let v = Version::parse("0.5.6-10-g1234abc").unwrap();
130+
assert_eq!(v, Version::new(0, 5, 6));
131+
}
132+
133+
#[test]
134+
fn test_parse_with_build_metadata() {
135+
let v = Version::parse("0.5.6+dcap.0").unwrap();
136+
assert_eq!(v, Version::new(0, 5, 6));
137+
}
138+
139+
#[test]
140+
fn test_parse_mixed() {
141+
let v = Version::parse("0.5.6-alpha.0+dcap.0").unwrap();
142+
assert_eq!(v, Version::new(0, 5, 6));
143+
}
144+
145+
#[test]
146+
fn test_parse_with_whitespace() {
147+
let v = Version::parse(" 0.5.6 ").unwrap();
148+
assert_eq!(v, Version::new(0, 5, 6));
149+
}
150+
151+
#[test]
152+
fn test_parse_empty() {
153+
assert!(Version::parse("").is_none());
154+
assert!(Version::parse(" ").is_none());
155+
}
156+
157+
#[test]
158+
fn test_parse_invalid() {
159+
assert!(Version::parse("invalid").is_none());
160+
assert!(Version::parse("1").is_none());
161+
assert!(Version::parse("v").is_none());
162+
assert!(Version::parse("abc.def.ghi").is_none());
163+
}
164+
165+
#[test]
166+
fn test_comparison() {
167+
assert!(Version::new(0, 5, 6) > Version::new(0, 5, 5));
168+
assert!(Version::new(0, 5, 6) < Version::new(0, 5, 7));
169+
assert!(Version::new(0, 5, 6) < Version::new(0, 6, 0));
170+
assert!(Version::new(0, 5, 6) < Version::new(1, 0, 0));
171+
assert!(Version::new(0, 5, 6) == Version::new(0, 5, 6));
172+
// Two segments comparison
173+
assert!(Version::new(0, 5, 0) < Version::new(0, 5, 6));
174+
}
175+
176+
#[test]
177+
fn test_display() {
178+
let v = Version::new(0, 5, 6);
179+
assert_eq!(v.to_string(), "0.5.6");
180+
}
181+
}

dstack-util/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ tempfile.workspace = true
5656
ez-hash.workspace = true
5757
cc-eventlog.workspace = true
5858
safe-write.workspace = true
59+
errify.workspace = true
5960

6061
[dev-dependencies]
6162
rand.workspace = true

0 commit comments

Comments
 (0)