github.com/vmware/go-vcloud-director/v2@v2.24.0/govcd/api_vcd_versions.go (about) 1 /* 2 * Copyright 2019 VMware, Inc. All rights reserved. Licensed under the Apache v2 License. 3 */ 4 5 package govcd 6 7 import ( 8 "fmt" 9 "net/http" 10 "regexp" 11 "sort" 12 "strings" 13 "time" 14 15 "github.com/araddon/dateparse" 16 semver "github.com/hashicorp/go-version" 17 18 "github.com/vmware/go-vcloud-director/v2/types/v56" 19 "github.com/vmware/go-vcloud-director/v2/util" 20 ) 21 22 type VersionInfo struct { 23 Version string `xml:"Version"` 24 LoginUrl string `xml:"LoginUrl"` 25 ProviderLoginUrl string `xml:"ProviderLoginUrl"` 26 Deprecated bool `xml:"deprecated,attr,omitempty"` 27 } 28 29 type VersionInfos []VersionInfo 30 31 type SupportedVersions struct { 32 VersionInfos `xml:"VersionInfo"` 33 } 34 35 // VcdVersion contains the full information about a VCD version 36 type VcdVersion struct { 37 Version *semver.Version 38 Time time.Time 39 } 40 41 // apiVersionToVcdVersion gets the vCD version from max supported API version 42 var apiVersionToVcdVersion = map[string]string{ 43 "29.0": "9.0", 44 "30.0": "9.1", 45 "31.0": "9.5", 46 "32.0": "9.7", 47 "33.0": "10.0", 48 "34.0": "10.1", 49 "35.0": "10.2", 50 "36.0": "10.3", 51 "37.0": "10.4", // Provisional version for non-GA release. It may change later 52 } 53 54 // vcdVersionToApiVersion gets the max supported API version from vCD version 55 var vcdVersionToApiVersion = map[string]string{ 56 "9.0": "29.0", 57 "9.1": "30.0", 58 "9.5": "31.0", 59 "9.7": "32.0", 60 "10.0": "33.0", 61 "10.1": "34.0", 62 "10.2": "35.0", 63 "10.3": "36.0", 64 "10.4": "37.0", // Provisional version for non-GA release. It may change later 65 } 66 67 // to make vcdVersionToApiVersion used 68 var _ = vcdVersionToApiVersion 69 70 // APIVCDMaxVersionIs compares against maximum vCD supported API version from /api/versions (not necessarily 71 // the currently used one). This allows to check what is the maximum API version that vCD instance 72 // supports and can be used to guess vCD product version. API 31.0 support was first introduced in 73 // vCD 9.5 (as per https://code.vmware.com/doc/preview?id=8072). Therefore APIMaxVerIs(">= 31.0") 74 // implies that you have vCD 9.5 or newer running inside. 75 // It does not require for the client to be authenticated. 76 // 77 // Format: ">= 27.0, < 32.0", ">= 30.0", "= 27.0" 78 // 79 // vCD version mapping to API version support https://code.vmware.com/doc/preview?id=8072 80 func (client *Client) APIVCDMaxVersionIs(versionConstraint string) bool { 81 err := client.vcdFetchSupportedVersions() 82 if err != nil { 83 util.Logger.Printf("[ERROR] could not retrieve supported versions: %s", err) 84 return false 85 } 86 87 util.Logger.Printf("[TRACE] checking max API version against constraints '%s'", versionConstraint) 88 maxVersion, err := client.MaxSupportedVersion() 89 if err != nil { 90 util.Logger.Printf("[ERROR] unable to find max supported version : %s", err) 91 return false 92 } 93 94 isSupported, err := client.apiVersionMatchesConstraint(maxVersion, versionConstraint) 95 if err != nil { 96 util.Logger.Printf("[ERROR] unable to find max supported version : %s", err) 97 return false 98 } 99 100 return isSupported 101 } 102 103 // APIClientVersionIs allows to compare against currently used API version VCDClient.Client.APIVersion. 104 // Can be useful to validate if a certain feature can be used or not. 105 // It does not require for the client to be authenticated. 106 // 107 // Format: ">= 27.0, < 32.0", ">= 30.0", "= 27.0" 108 // 109 // vCD version mapping to API version support https://code.vmware.com/doc/preview?id=8072 110 func (client *Client) APIClientVersionIs(versionConstraint string) bool { 111 112 util.Logger.Printf("[TRACE] checking current API version against constraints '%s'", versionConstraint) 113 114 isSupported, err := client.apiVersionMatchesConstraint(client.APIVersion, versionConstraint) 115 if err != nil { 116 util.Logger.Printf("[ERROR] unable to find supported version : %s", err) 117 return false 118 } 119 120 return isSupported 121 } 122 123 // vcdFetchSupportedVersions retrieves list of supported versions from 124 // /api/versions endpoint and stores them in VCDClient for future uses. 125 // It only does it once. 126 func (client *Client) vcdFetchSupportedVersions() error { 127 // Only fetch /versions if it is not stored already 128 numVersions := len(client.supportedVersions.VersionInfos) 129 if numVersions > 0 { 130 util.Logger.Printf("[TRACE] skipping fetch of versions because %d are stored", numVersions) 131 return nil 132 } 133 134 apiEndpoint := client.VCDHREF 135 apiEndpoint.Path += "/versions" 136 137 suppVersions := new(SupportedVersions) 138 _, err := client.ExecuteRequest(apiEndpoint.String(), http.MethodGet, 139 "", "error fetching versions: %s", nil, suppVersions) 140 141 client.supportedVersions = *suppVersions 142 143 // Log all supported API versions in one line to help identify vCD version from logs 144 allApiVersions := make([]string, len(client.supportedVersions.VersionInfos)) 145 for versionIndex, version := range client.supportedVersions.VersionInfos { 146 allApiVersions[versionIndex] = version.Version 147 } 148 util.Logger.Printf("[DEBUG] supported API versions : %s", strings.Join(allApiVersions, ",")) 149 150 return err 151 } 152 153 // MaxSupportedVersion parses supported version list and returns the highest version in string format. 154 func (client *Client) MaxSupportedVersion() (string, error) { 155 versions := make([]*semver.Version, len(client.supportedVersions.VersionInfos)) 156 for index, versionInfo := range client.supportedVersions.VersionInfos { 157 version, err := semver.NewVersion(versionInfo.Version) 158 if err != nil { 159 return "", fmt.Errorf("error parsing version %s: %s", versionInfo.Version, err) 160 } 161 versions[index] = version 162 } 163 // Sort supported versions in order lowest-highest 164 sort.Sort(semver.Collection(versions)) 165 166 switch { 167 case len(versions) > 1: 168 return versions[len(versions)-1].Original(), nil 169 case len(versions) == 1: 170 return versions[0].Original(), nil 171 default: 172 return "", fmt.Errorf("could not identify supported versions") 173 } 174 } 175 176 // vcdCheckSupportedVersion checks if there is at least one specified version exactly matching listed ones. 177 // Format example "27.0" 178 func (client *Client) vcdCheckSupportedVersion(version string) error { 179 return client.checkSupportedVersionConstraint(fmt.Sprintf("= %s", version)) 180 } 181 182 // Checks if there is at least one specified version matching the list returned by vCD. 183 // Constraint format can be in format ">= 27.0, < 32",">= 30" ,"= 27.0". 184 func (client *Client) checkSupportedVersionConstraint(versionConstraint string) error { 185 for _, versionInfo := range client.supportedVersions.VersionInfos { 186 versionMatch, err := client.apiVersionMatchesConstraint(versionInfo.Version, versionConstraint) 187 if err != nil { 188 return fmt.Errorf("cannot match version: %s", err) 189 } 190 191 if versionMatch { 192 return nil 193 } 194 } 195 return fmt.Errorf("version %s is not supported", versionConstraint) 196 } 197 198 func (client *Client) apiVersionMatchesConstraint(version, versionConstraint string) (bool, error) { 199 200 checkVer, err := semver.NewVersion(version) 201 if err != nil { 202 return false, fmt.Errorf("[ERROR] unable to parse version %s : %s", version, err) 203 } 204 // Create a provided constraint to check against current max version 205 constraints, err := semver.NewConstraint(versionConstraint) 206 if err != nil { 207 return false, fmt.Errorf("[ERROR] unable to parse given version constraint '%s' : %s", versionConstraint, err) 208 } 209 if constraints.Check(checkVer) { 210 util.Logger.Printf("[INFO] API version %s satisfies constraints '%s'", checkVer, constraints) 211 return true, nil 212 } 213 214 util.Logger.Printf("[TRACE] API version %s does not satisfy constraints '%s'", checkVer, constraints) 215 return false, nil 216 } 217 218 // validateAPIVersion fetches API versions 219 func (client *Client) validateAPIVersion() error { 220 err := client.vcdFetchSupportedVersions() 221 if err != nil { 222 return fmt.Errorf("could not retrieve supported versions: %s", err) 223 } 224 225 // Check if version is supported 226 err = client.vcdCheckSupportedVersion(client.APIVersion) 227 if err != nil { 228 return fmt.Errorf("API version %s is not supported: %s", client.APIVersion, err) 229 } 230 231 return nil 232 } 233 234 // GetSpecificApiVersionOnCondition returns default version or wantedApiVersion if it is connected to version 235 // described in vcdApiVersionCondition 236 // f.e. values ">= 32.0", "32.0" returns 32.0 if vCD version is above or 9.7 237 func (client *Client) GetSpecificApiVersionOnCondition(vcdApiVersionCondition, wantedApiVersion string) string { 238 apiVersion := client.APIVersion 239 if client.APIVCDMaxVersionIs(vcdApiVersionCondition) { 240 apiVersion = wantedApiVersion 241 } 242 return apiVersion 243 } 244 245 // GetVcdVersion finds the VCD version and the time of build 246 func (client *Client) GetVcdVersion() (string, time.Time, error) { 247 248 path := client.VCDHREF 249 path.Path += "/admin" 250 var admin types.VCloud 251 _, err := client.ExecuteRequest(path.String(), http.MethodGet, 252 "", "error retrieving admin info: %s", nil, &admin) 253 if err != nil { 254 return "", time.Time{}, err 255 } 256 description := admin.Description 257 258 if description == "" { 259 return "", time.Time{}, fmt.Errorf("no version information found") 260 } 261 reVersion := regexp.MustCompile(`^\s*(\S+)\s+(.*)`) 262 263 versionList := reVersion.FindAllStringSubmatch(description, -1) 264 265 if len(versionList) == 0 || len(versionList[0]) < 2 { 266 return "", time.Time{}, fmt.Errorf("error getting version information from description %s", description) 267 } 268 version := versionList[0][1] 269 versionDate := versionList[0][2] 270 versionTime, err := dateparse.ParseStrict(versionDate) 271 if err != nil { 272 return "", time.Time{}, fmt.Errorf("[version %s] could not convert date %s to formal date: %s", version, versionDate, err) 273 } 274 275 return version, versionTime, nil 276 } 277 278 // GetVcdShortVersion returns the VCD version (three digits, no build info) 279 func (client *Client) GetVcdShortVersion() (string, error) { 280 281 vcdVersion, err := client.GetVcdFullVersion() 282 if err != nil { 283 return "", fmt.Errorf("error getting version digits: %s", err) 284 } 285 digits := vcdVersion.Version.Segments() 286 return fmt.Sprintf("%d.%d.%d", digits[0], digits[1], digits[2]), nil 287 } 288 289 // GetVcdFullVersion returns the full VCD version information as a structure 290 func (client *Client) GetVcdFullVersion() (VcdVersion, error) { 291 var vcdVersion VcdVersion 292 version, versionTime, err := client.GetVcdVersion() 293 if err != nil { 294 return VcdVersion{}, err 295 } 296 297 vcdVersion.Version, err = semver.NewVersion(version) 298 if err != nil { 299 return VcdVersion{}, err 300 } 301 if len(vcdVersion.Version.Segments()) < 4 { 302 return VcdVersion{}, fmt.Errorf("error getting version digits from version %s", version) 303 } 304 vcdVersion.Time = versionTime 305 return vcdVersion, nil 306 } 307 308 // intListToVersion converts a list of integers into a dot-separated string 309 func intListToVersion(digits []int, atMost int) string { 310 result := "" 311 for i, digit := range digits { 312 if result != "" { 313 result += "." 314 } 315 if i >= atMost { 316 result += "0" 317 } else { 318 result += fmt.Sprintf("%d", digit) 319 } 320 } 321 return result 322 } 323 324 // VersionEqualOrGreater return true if the current version is the same or greater than the one being compared. 325 // If howManyDigits is > 3, the comparison includes the build. 326 // Examples: 327 // 328 // client version is 1.2.3.1234 329 // compare version is 1.2.3.2000 330 // 331 // function return true if howManyDigits is <= 3, but false if howManyDigits is > 3 332 // 333 // client version is 1.2.3.1234 334 // compare version is 1.1.1.0 335 // 336 // function returns true regardless of value of howManyDigits 337 func (client *Client) VersionEqualOrGreater(compareTo string, howManyDigits int) (bool, error) { 338 339 fullVersion, err := client.GetVcdFullVersion() 340 if err != nil { 341 return false, err 342 } 343 compareToVersion, err := semver.NewVersion(compareTo) 344 if err != nil { 345 return false, err 346 } 347 if howManyDigits < 4 { 348 currentString := intListToVersion(fullVersion.Version.Segments(), howManyDigits) 349 compareToString := intListToVersion(compareToVersion.Segments(), howManyDigits) 350 fullVersion.Version, err = semver.NewVersion(currentString) 351 if err != nil { 352 return false, err 353 } 354 compareToVersion, err = semver.NewVersion(compareToString) 355 if err != nil { 356 return false, err 357 } 358 } 359 360 return fullVersion.Version.GreaterThanOrEqual(compareToVersion), nil 361 }