sigs.k8s.io/kubebuilder/v3@v3.14.0/pkg/plugins/golang/go_version.go (about)

     1  /*
     2  Copyright 2018 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package golang
    18  
    19  import (
    20  	"fmt"
    21  	"os/exec"
    22  	"regexp"
    23  	"strconv"
    24  	"strings"
    25  )
    26  
    27  const (
    28  	goVerPattern = `^go(?P<major>[0-9]+)\.(?P<minor>[0-9]+)(?:\.(?P<patch>[0-9]+)|(?P<pre>(?:alpha|beta|rc)[0-9]+))?$`
    29  )
    30  
    31  var (
    32  	goVerRegexp = regexp.MustCompile(goVerPattern)
    33  )
    34  
    35  // GoVersion describes a Go version.
    36  type GoVersion struct {
    37  	major, minor, patch int
    38  	prerelease          string
    39  }
    40  
    41  func (v GoVersion) String() string {
    42  	switch {
    43  	case v.patch != 0:
    44  		return fmt.Sprintf("go%d.%d.%d", v.major, v.minor, v.patch)
    45  	case v.prerelease != "":
    46  		return fmt.Sprintf("go%d.%d%s", v.major, v.minor, v.prerelease)
    47  	}
    48  	return fmt.Sprintf("go%d.%d", v.major, v.minor)
    49  }
    50  
    51  // MustParse will panic if verStr does not match the expected Go version string spec.
    52  func MustParse(verStr string) (v GoVersion) {
    53  	if err := v.parse(verStr); err != nil {
    54  		panic(err)
    55  	}
    56  	return v
    57  }
    58  
    59  func (v *GoVersion) parse(verStr string) error {
    60  	m := goVerRegexp.FindStringSubmatch(verStr)
    61  	if m == nil {
    62  		return fmt.Errorf("invalid version string")
    63  	}
    64  
    65  	var err error
    66  
    67  	v.major, err = strconv.Atoi(m[1])
    68  	if err != nil {
    69  		return fmt.Errorf("error parsing major version '%s': %s", m[1], err)
    70  	}
    71  
    72  	v.minor, err = strconv.Atoi(m[2])
    73  	if err != nil {
    74  		return fmt.Errorf("error parsing minor version '%s': %s", m[2], err)
    75  	}
    76  
    77  	if m[3] != "" {
    78  		v.patch, err = strconv.Atoi(m[3])
    79  		if err != nil {
    80  			return fmt.Errorf("error parsing patch version '%s': %s", m[2], err)
    81  		}
    82  	}
    83  
    84  	v.prerelease = m[4]
    85  
    86  	return nil
    87  }
    88  
    89  // Compare returns -1, 0, or 1 if v < other, v == other, or v > other, respectively.
    90  func (v GoVersion) Compare(other GoVersion) int {
    91  	if v.major > other.major {
    92  		return 1
    93  	}
    94  	if v.major < other.major {
    95  		return -1
    96  	}
    97  
    98  	if v.minor > other.minor {
    99  		return 1
   100  	}
   101  	if v.minor < other.minor {
   102  		return -1
   103  	}
   104  
   105  	if v.patch > other.patch {
   106  		return 1
   107  	}
   108  	if v.patch < other.patch {
   109  		return -1
   110  	}
   111  
   112  	if v.prerelease == other.prerelease {
   113  		return 0
   114  	}
   115  	if v.prerelease == "" {
   116  		return 1
   117  	}
   118  	if other.prerelease == "" {
   119  		return -1
   120  	}
   121  	if v.prerelease > other.prerelease {
   122  		return 1
   123  	}
   124  	return -1
   125  }
   126  
   127  // ValidateGoVersion verifies that Go is installed and the current go version is supported by a plugin.
   128  func ValidateGoVersion(min, max GoVersion) error {
   129  	err := fetchAndCheckGoVersion(min, max)
   130  	if err != nil {
   131  		return fmt.Errorf("%s. You can skip this check using the --skip-go-version-check flag", err)
   132  	}
   133  	return nil
   134  }
   135  
   136  func fetchAndCheckGoVersion(min, max GoVersion) error {
   137  	cmd := exec.Command("go", "version")
   138  	out, err := cmd.Output()
   139  	if err != nil {
   140  		return fmt.Errorf("failed to retrieve 'go version': %v", string(out))
   141  	}
   142  
   143  	split := strings.Split(string(out), " ")
   144  	if len(split) < 3 {
   145  		return fmt.Errorf("found invalid Go version: %q", string(out))
   146  	}
   147  	goVer := split[2]
   148  	if err := checkGoVersion(goVer, min, max); err != nil {
   149  		return fmt.Errorf("go version '%s' is incompatible because '%s'", goVer, err)
   150  	}
   151  	return nil
   152  }
   153  
   154  func checkGoVersion(verStr string, min, max GoVersion) error {
   155  	var version GoVersion
   156  	if err := version.parse(verStr); err != nil {
   157  		return err
   158  	}
   159  
   160  	if version.Compare(min) < 0 || version.Compare(max) >= 0 {
   161  		return fmt.Errorf("plugin requires %s <= version < %s", min, max)
   162  	}
   163  
   164  	return nil
   165  }