@@ -2,6 +2,7 @@ package utils
22
33import (
44 "fmt"
5+ "strconv"
56 "strings"
67
78 "github.com/gophercloud/gophercloud"
@@ -20,9 +21,12 @@ var goodStatus = map[string]bool{
2021 "stable" : true ,
2122}
2223
23- // ChooseVersion queries the base endpoint of an API to choose the most recent non-experimental alternative from a service's
24- // published versions.
25- // It returns the highest-Priority Version among the alternatives that are provided, as well as its corresponding endpoint.
24+ // ChooseVersion queries the base endpoint of an API to choose the identity service version.
25+ // It will pick a version among the recognized, taking into account the priority and avoiding
26+ // experimental alternatives from the published versions. However, if the client specifies a full
27+ // endpoint that is among the recognized versions, it will be used regardless of priority.
28+ // It returns the highest-Priority Version, OR exact match with client endpoint,
29+ // among the alternatives that are provided, as well as its corresponding endpoint.
2630func ChooseVersion (client * gophercloud.ProviderClient , recognized []* Version ) (* Version , string , error ) {
2731 type linkResp struct {
2832 Href string `json:"href"`
@@ -109,3 +113,123 @@ func ChooseVersion(client *gophercloud.ProviderClient, recognized []*Version) (*
109113
110114 return highest , endpoint , nil
111115}
116+
117+ type SupportedMicroversions struct {
118+ MaxMajor int
119+ MaxMinor int
120+ MinMajor int
121+ MinMinor int
122+ }
123+
124+ // GetSupportedMicroversions returns the minimum and maximum microversion that is supported by the ServiceClient Endpoint.
125+ func GetSupportedMicroversions (client * gophercloud.ServiceClient ) (SupportedMicroversions , error ) {
126+ type valueResp struct {
127+ ID string `json:"id"`
128+ Status string `json:"status"`
129+ Version string `json:"version"`
130+ MinVersion string `json:"min_version"`
131+ }
132+
133+ type response struct {
134+ Version valueResp `json:"version"`
135+ Versions []valueResp `json:"versions"`
136+ }
137+ var minVersion , maxVersion string
138+ var supportedMicroversions SupportedMicroversions
139+ var resp response
140+ _ , err := client .Get (client .Endpoint , & resp , & gophercloud.RequestOpts {
141+ OkCodes : []int {200 , 300 },
142+ })
143+
144+ if err != nil {
145+ return supportedMicroversions , err
146+ }
147+
148+ if len (resp .Versions ) > 0 {
149+ // We are dealing with an unversioned endpoint
150+ // We only handle the case when there is exactly one, and assume it is the correct one
151+ if len (resp .Versions ) > 1 {
152+ return supportedMicroversions , fmt .Errorf ("unversioned endpoint with multiple alternatives not supported" )
153+ }
154+ minVersion = resp .Versions [0 ].MinVersion
155+ maxVersion = resp .Versions [0 ].Version
156+ } else {
157+ minVersion = resp .Version .MinVersion
158+ maxVersion = resp .Version .Version
159+ }
160+
161+ // Return early if the endpoint does not support microversions
162+ if minVersion == "" && maxVersion == "" {
163+ return supportedMicroversions , fmt .Errorf ("microversions not supported by ServiceClient Endpoint" )
164+ }
165+
166+ supportedMicroversions .MinMajor , supportedMicroversions .MinMinor , err = ParseMicroversion (minVersion )
167+ if err != nil {
168+ return supportedMicroversions , err
169+ }
170+
171+ supportedMicroversions .MaxMajor , supportedMicroversions .MaxMinor , err = ParseMicroversion (maxVersion )
172+ if err != nil {
173+ return supportedMicroversions , err
174+ }
175+
176+ return supportedMicroversions , nil
177+ }
178+
179+ // RequireMicroversion checks that the required microversion is supported and
180+ // returns a ServiceClient with the microversion set.
181+ func RequireMicroversion (client gophercloud.ServiceClient , required string ) (gophercloud.ServiceClient , error ) {
182+ supportedMicroversions , err := GetSupportedMicroversions (& client )
183+ if err != nil {
184+ return client , fmt .Errorf ("unable to determine supported microversions: %w" , err )
185+ }
186+ supported , err := supportedMicroversions .IsSupported (required )
187+ if err != nil {
188+ return client , err
189+ }
190+ if ! supported {
191+ return client , fmt .Errorf ("microversion %s not supported. Supported versions: %v" , required , supportedMicroversions )
192+ }
193+ client .Microversion = required
194+ return client , nil
195+ }
196+
197+ // IsSupported checks if a microversion falls in the supported interval.
198+ // It returns true if the version is within the interval and false otherwise.
199+ func (supported SupportedMicroversions ) IsSupported (version string ) (bool , error ) {
200+ // Parse the version X.Y into X and Y integers that are easier to compare.
201+ vMajor , vMinor , err := ParseMicroversion (version )
202+ if err != nil {
203+ return false , err
204+ }
205+
206+ // Check that the major version number is supported.
207+ if (vMajor < supported .MinMajor ) || (vMajor > supported .MaxMajor ) {
208+ return false , nil
209+ }
210+
211+ // Check that the minor version number is supported
212+ if (vMinor <= supported .MaxMinor ) && (vMinor >= supported .MinMinor ) {
213+ return true , nil
214+ }
215+
216+ return false , nil
217+ }
218+
219+ // ParseMicroversion parses the version major.minor into separate integers major and minor.
220+ // For example, "2.53" becomes 2 and 53.
221+ func ParseMicroversion (version string ) (major int , minor int , err error ) {
222+ parts := strings .Split (version , "." )
223+ if len (parts ) != 2 {
224+ return 0 , 0 , fmt .Errorf ("invalid microversion format: %q" , version )
225+ }
226+ major , err = strconv .Atoi (parts [0 ])
227+ if err != nil {
228+ return 0 , 0 , err
229+ }
230+ minor , err = strconv .Atoi (parts [1 ])
231+ if err != nil {
232+ return 0 , 0 , err
233+ }
234+ return major , minor , nil
235+ }
0 commit comments