@@ -45,23 +45,34 @@ pub fn ovmf_variant_for_version(version: &str) -> Result<OvmfVariant> {
4545 } )
4646}
4747
48- /// Extract a dotted version suffix (e.g. "0.5.10") from a dstack image name like
49- /// `dstack-0.5.10`, `dstack-dev-0.5.10`, or `dstack-nvidia-0.5.10`.
48+ /// Extract the `MAJOR.MINOR.PATCH` version suffix from a dstack image name.
5049///
51- /// Returns `None` when the image name does not end with a recognisable
52- /// `MAJOR.MINOR.PATCH` segment.
50+ /// Recognises any `<prefix>-MAJOR.MINOR.PATCH[.SUFFIX]` shape, e.g.
51+ /// `dstack-0.5.10`, `dstack-dev-0.5.10`, `dstack-nvidia-0.5.10`,
52+ /// `dstack-nvidia-dev-0.5.10`, `dstack-0.5.10.rc1`, `dstack-dev-0.6.1.dev`.
53+ ///
54+ /// The optional `.SUFFIX` is permitted to be non-numeric (pre-release tag,
55+ /// build label, etc.) and is dropped from the returned slice — only the
56+ /// numeric `X.Y.Z` is needed to pick the OVMF variant.
57+ ///
58+ /// Returns `None` when the segment after the last `-` is not at least a valid
59+ /// `X.Y.Z` triple of non-empty numeric components.
5360pub fn extract_version_from_image_name ( image : & str ) -> Option < & str > {
5461 let tail = image. rsplit ( '-' ) . next ( ) ?;
5562 let parts: Vec < & str > = tail. split ( '.' ) . collect ( ) ;
56- if parts. len ( ) == 3
57- && parts
58- . iter ( )
59- . all ( |p| !p. is_empty ( ) && p. parse :: < u32 > ( ) . is_ok ( ) )
60- {
61- Some ( tail)
62- } else {
63- None
63+ if !( 3 ..=4 ) . contains ( & parts. len ( ) ) {
64+ return None ;
65+ }
66+ let core_numeric = parts[ ..3 ]
67+ . iter ( )
68+ . all ( |p| !p. is_empty ( ) && p. parse :: < u32 > ( ) . is_ok ( ) ) ;
69+ let suffix_ok = parts. len ( ) == 3 || !parts[ 3 ] . is_empty ( ) ;
70+ if !( core_numeric && suffix_ok) {
71+ return None ;
6472 }
73+ // Slice off the optional `.SUFFIX` so callers get just `X.Y.Z`.
74+ let core_len = parts[ 0 ] . len ( ) + 1 + parts[ 1 ] . len ( ) + 1 + parts[ 2 ] . len ( ) ;
75+ Some ( & tail[ ..core_len] )
6576}
6677
6778/// Pick the OVMF variant from an image name like `dstack-0.5.10`.
@@ -111,6 +122,7 @@ mod ovmf_variant_tests {
111122
112123 #[ test]
113124 fn parses_version_from_image_name ( ) {
125+ // Three-segment plain versions across the known prefix shapes.
114126 assert_eq ! (
115127 extract_version_from_image_name( "dstack-0.5.10" ) ,
116128 Some ( "0.5.10" )
@@ -127,9 +139,27 @@ mod ovmf_variant_tests {
127139 extract_version_from_image_name( "dstack-nvidia-dev-0.6.1" ) ,
128140 Some ( "0.6.1" )
129141 ) ;
142+ // Optional .SUFFIX is allowed and dropped; suffix may be non-numeric.
143+ assert_eq ! (
144+ extract_version_from_image_name( "dstack-0.5.10.rc1" ) ,
145+ Some ( "0.5.10" )
146+ ) ;
147+ assert_eq ! (
148+ extract_version_from_image_name( "dstack-dev-0.6.1.dev" ) ,
149+ Some ( "0.6.1" )
150+ ) ;
151+ assert_eq ! (
152+ extract_version_from_image_name( "dstack-nvidia-dev-0.5.10.1" ) ,
153+ Some ( "0.5.10" )
154+ ) ;
155+ // Rejections.
130156 assert_eq ! ( extract_version_from_image_name( "dstack" ) , None ) ;
131157 assert_eq ! ( extract_version_from_image_name( "dstack-rc1" ) , None ) ;
132158 assert_eq ! ( extract_version_from_image_name( "dstack-0.5" ) , None ) ;
159+ assert_eq ! ( extract_version_from_image_name( "dstack-0.5.10." ) , None ) ;
160+ assert_eq ! ( extract_version_from_image_name( "dstack-0.5.10.1.2" ) , None ) ;
161+ assert_eq ! ( extract_version_from_image_name( "dstack-0..10" ) , None ) ;
162+ assert_eq ! ( extract_version_from_image_name( "dstack-a.b.c" ) , None ) ;
133163 }
134164
135165 #[ test]
0 commit comments