22//
33// SPDX-License-Identifier: Apache-2.0
44
5+ use anyhow:: { bail, Context , Result } ;
56use serde:: { Deserialize , Serialize } ;
67use serde_human_bytes as hex_bytes;
78
9+ pub use dstack_types:: OvmfVariant ;
810pub use machine:: { Machine , TdxMeasurementDetails } ;
911
1012use util:: { measure_log, measure_sha384, utf16_encode} ;
@@ -17,8 +19,188 @@ mod kernel;
1719mod machine;
1820mod num;
1921mod tdvf;
22+ mod uefi_var;
2023mod util;
2124
25+ /// Pick the OVMF variant for a given dstack OS version string ("MAJOR.MINOR.PATCH").
26+ ///
27+ /// Treats `0.5.10 <= v < 0.6.0` and `v >= 0.6.1` as `Stable202505`, everything else as
28+ /// `Pre202505`. Used as a fallback when `VmConfig::ovmf_variant` is absent.
29+ pub fn ovmf_variant_for_version ( version : & str ) -> Result < OvmfVariant > {
30+ let parts: Vec < u32 > = version
31+ . split ( '.' )
32+ . map ( |p| {
33+ p. parse :: < u32 > ( )
34+ . with_context ( || format ! ( "invalid version component: {p}" ) )
35+ } )
36+ . collect :: < Result < Vec < _ > , _ > > ( ) ?;
37+ if parts. len ( ) != 3 {
38+ bail ! ( "expected MAJOR.MINOR.PATCH, got {version}" ) ;
39+ }
40+ let v = ( parts[ 0 ] , parts[ 1 ] , parts[ 2 ] ) ;
41+ let stable = ( ( 0 , 5 , 10 ) ..( 0 , 6 , 0 ) ) . contains ( & v) || v >= ( 0 , 6 , 1 ) ;
42+ Ok ( if stable {
43+ OvmfVariant :: Stable202505
44+ } else {
45+ OvmfVariant :: Pre202505
46+ } )
47+ }
48+
49+ /// Extract the `MAJOR.MINOR.PATCH` version suffix from a dstack image name.
50+ ///
51+ /// Recognises any `<prefix>-MAJOR.MINOR.PATCH[.SUFFIX]` shape, e.g.
52+ /// `dstack-0.5.10`, `dstack-dev-0.5.10`, `dstack-nvidia-0.5.10`,
53+ /// `dstack-nvidia-dev-0.5.10`, `dstack-0.5.10.rc1`, `dstack-dev-0.6.1.dev`.
54+ ///
55+ /// The optional `.SUFFIX` is permitted to be non-numeric (pre-release tag,
56+ /// build label, etc.) and is dropped from the returned slice — only the
57+ /// numeric `X.Y.Z` is needed to pick the OVMF variant.
58+ ///
59+ /// Returns `None` when the segment after the last `-` is not at least a valid
60+ /// `X.Y.Z` triple of non-empty numeric components.
61+ pub fn extract_version_from_image_name ( image : & str ) -> Option < & str > {
62+ let tail = image. rsplit ( '-' ) . next ( ) ?;
63+ let parts: Vec < & str > = tail. split ( '.' ) . collect ( ) ;
64+ if !( 3 ..=4 ) . contains ( & parts. len ( ) ) {
65+ return None ;
66+ }
67+ let core_numeric = parts[ ..3 ]
68+ . iter ( )
69+ . all ( |p| !p. is_empty ( ) && p. parse :: < u32 > ( ) . is_ok ( ) ) ;
70+ let suffix_ok = parts. len ( ) == 3 || !parts[ 3 ] . is_empty ( ) ;
71+ if !( core_numeric && suffix_ok) {
72+ return None ;
73+ }
74+ // Slice off the optional `.SUFFIX` so callers get just `X.Y.Z`.
75+ let core_len = parts[ 0 ] . len ( ) + 1 + parts[ 1 ] . len ( ) + 1 + parts[ 2 ] . len ( ) ;
76+ Some ( & tail[ ..core_len] )
77+ }
78+
79+ /// Pick the OVMF variant from an image name like `dstack-0.5.10`.
80+ ///
81+ /// Falls back to `OvmfVariant::default()` (= `Pre202505`) when the image name is
82+ /// missing or doesn't carry a parseable version suffix. Use this only as a
83+ /// fallback for images that pre-date `VmConfig::ovmf_variant`.
84+ pub fn ovmf_variant_for_image ( image : Option < & str > ) -> OvmfVariant {
85+ image
86+ . and_then ( extract_version_from_image_name)
87+ . and_then ( |v| ovmf_variant_for_version ( v) . ok ( ) )
88+ . unwrap_or_default ( )
89+ }
90+
91+ #[ cfg( test) ]
92+ mod ovmf_variant_tests {
93+ use super :: * ;
94+
95+ #[ test]
96+ fn pre_202505_for_old_versions ( ) {
97+ for v in [ "0.4.99" , "0.5.7" , "0.5.8" , "0.5.9" , "0.6.0" ] {
98+ assert_eq ! (
99+ ovmf_variant_for_version( v) . unwrap( ) ,
100+ OvmfVariant :: Pre202505 ,
101+ "{v}"
102+ ) ;
103+ }
104+ }
105+
106+ #[ test]
107+ fn stable_202505_for_new_versions ( ) {
108+ for v in [ "0.5.10" , "0.5.99" , "0.6.1" , "0.6.2" , "0.7.0" , "1.0.0" ] {
109+ assert_eq ! (
110+ ovmf_variant_for_version( v) . unwrap( ) ,
111+ OvmfVariant :: Stable202505 ,
112+ "{v}"
113+ ) ;
114+ }
115+ }
116+
117+ #[ test]
118+ fn rejects_malformed_version ( ) {
119+ assert ! ( ovmf_variant_for_version( "0.5" ) . is_err( ) ) ;
120+ assert ! ( ovmf_variant_for_version( "0.5.10-dev" ) . is_err( ) ) ;
121+ assert ! ( ovmf_variant_for_version( "v0.5.10" ) . is_err( ) ) ;
122+ }
123+
124+ #[ test]
125+ fn parses_version_from_image_name ( ) {
126+ // Three-segment plain versions across the known prefix shapes.
127+ assert_eq ! (
128+ extract_version_from_image_name( "dstack-0.5.10" ) ,
129+ Some ( "0.5.10" )
130+ ) ;
131+ assert_eq ! (
132+ extract_version_from_image_name( "dstack-dev-0.5.10" ) ,
133+ Some ( "0.5.10" )
134+ ) ;
135+ assert_eq ! (
136+ extract_version_from_image_name( "dstack-nvidia-0.5.10" ) ,
137+ Some ( "0.5.10" )
138+ ) ;
139+ assert_eq ! (
140+ extract_version_from_image_name( "dstack-nvidia-dev-0.6.1" ) ,
141+ Some ( "0.6.1" )
142+ ) ;
143+ // Optional .SUFFIX is allowed and dropped; suffix may be non-numeric.
144+ assert_eq ! (
145+ extract_version_from_image_name( "dstack-0.5.10.rc1" ) ,
146+ Some ( "0.5.10" )
147+ ) ;
148+ assert_eq ! (
149+ extract_version_from_image_name( "dstack-dev-0.6.1.dev" ) ,
150+ Some ( "0.6.1" )
151+ ) ;
152+ assert_eq ! (
153+ extract_version_from_image_name( "dstack-nvidia-dev-0.5.10.1" ) ,
154+ Some ( "0.5.10" )
155+ ) ;
156+ // Rejections.
157+ assert_eq ! ( extract_version_from_image_name( "dstack" ) , None ) ;
158+ assert_eq ! ( extract_version_from_image_name( "dstack-rc1" ) , None ) ;
159+ assert_eq ! ( extract_version_from_image_name( "dstack-0.5" ) , None ) ;
160+ assert_eq ! ( extract_version_from_image_name( "dstack-0.5.10." ) , None ) ;
161+ assert_eq ! ( extract_version_from_image_name( "dstack-0.5.10.1.2" ) , None ) ;
162+ assert_eq ! ( extract_version_from_image_name( "dstack-0..10" ) , None ) ;
163+ assert_eq ! ( extract_version_from_image_name( "dstack-a.b.c" ) , None ) ;
164+ }
165+
166+ #[ test]
167+ fn ovmf_variant_for_image_handles_missing_and_unknown ( ) {
168+ assert_eq ! ( ovmf_variant_for_image( None ) , OvmfVariant :: Pre202505 ) ;
169+ assert_eq ! (
170+ ovmf_variant_for_image( Some ( "dstack" ) ) ,
171+ OvmfVariant :: Pre202505
172+ ) ;
173+ assert_eq ! (
174+ ovmf_variant_for_image( Some ( "dstack-0.5.9" ) ) ,
175+ OvmfVariant :: Pre202505
176+ ) ;
177+ assert_eq ! (
178+ ovmf_variant_for_image( Some ( "dstack-0.5.10" ) ) ,
179+ OvmfVariant :: Stable202505
180+ ) ;
181+ assert_eq ! (
182+ ovmf_variant_for_image( Some ( "dstack-nvidia-dev-0.6.1" ) ) ,
183+ OvmfVariant :: Stable202505
184+ ) ;
185+ }
186+
187+ #[ test]
188+ fn serializes_with_snake_case ( ) {
189+ assert_eq ! (
190+ serde_json:: to_string( & OvmfVariant :: Pre202505 ) . unwrap( ) ,
191+ "\" pre202505\" "
192+ ) ;
193+ assert_eq ! (
194+ serde_json:: to_string( & OvmfVariant :: Stable202505 ) . unwrap( ) ,
195+ "\" stable202505\" "
196+ ) ;
197+ assert_eq ! (
198+ serde_json:: from_str:: <OvmfVariant >( "\" stable202505\" " ) . unwrap( ) ,
199+ OvmfVariant :: Stable202505
200+ ) ;
201+ }
202+ }
203+
22204/// Contains all the measurement values for TDX.
23205#[ derive( Debug , Clone , Serialize , Deserialize ) ]
24206pub struct TdxMeasurements {
0 commit comments