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  }