github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/version/version.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  // The version package implements version parsing.
     5  // It also acts as guardian of the current client Juju version number.
     6  package version
     7  
     8  import (
     9  	"encoding/json"
    10  	"fmt"
    11  	"io/ioutil"
    12  	"os"
    13  	"path/filepath"
    14  	"regexp"
    15  	"runtime"
    16  	"strconv"
    17  	"strings"
    18  
    19  	"github.com/juju/utils/arch"
    20  	"github.com/juju/utils/series"
    21  	"gopkg.in/mgo.v2/bson"
    22  )
    23  
    24  // The presence and format of this constant is very important.
    25  // The debian/rules build recipe uses this value for the version
    26  // number of the release package.
    27  const version = "1.26-alpha1"
    28  
    29  // The version that we switched over from old style numbering to new style.
    30  var switchOverVersion = MustParse("1.19.9")
    31  
    32  // osReleaseFile is the name of the file that is read in order to determine
    33  // the linux type release version.
    34  var osReleaseFile = "/etc/os-release"
    35  
    36  // Current gives the current version of the system.  If the file
    37  // "FORCE-VERSION" is present in the same directory as the running
    38  // binary, it will override this.
    39  var Current = Binary{
    40  	Number: MustParse(version),
    41  	Series: series.HostSeries(),
    42  	Arch:   arch.HostArch(),
    43  }
    44  
    45  var Compiler = runtime.Compiler
    46  
    47  func init() {
    48  	toolsDir := filepath.Dir(os.Args[0])
    49  	v, err := ioutil.ReadFile(filepath.Join(toolsDir, "FORCE-VERSION"))
    50  	if err != nil {
    51  		if !os.IsNotExist(err) {
    52  			fmt.Fprintf(os.Stderr, "WARNING: cannot read forced version: %v\n", err)
    53  		}
    54  		return
    55  	}
    56  	Current.Number = MustParse(strings.TrimSpace(string(v)))
    57  }
    58  
    59  // Number represents a juju version.  When bugs are fixed the patch number is
    60  // incremented; when new features are added the minor number is incremented
    61  // and patch is reset; and when compatibility is broken the major version is
    62  // incremented and minor and patch are reset.  The build number is
    63  // automatically assigned and has no well defined sequence.  If the build
    64  // number is greater than zero or the tag is non-empty it indicates that the
    65  // release is still in development.  For versions older than 1.19.3,
    66  // development releases were indicated by an odd Minor number of any non-zero
    67  // build number.
    68  type Number struct {
    69  	Major int
    70  	Minor int
    71  	Tag   string
    72  	Patch int
    73  	Build int
    74  }
    75  
    76  // Zero is occasionally convenient and readable.
    77  // Please don't change its value.
    78  var Zero = Number{}
    79  
    80  // Binary specifies a binary version of juju.
    81  type Binary struct {
    82  	Number
    83  	Series string
    84  	Arch   string
    85  }
    86  
    87  func (v Binary) String() string {
    88  	return fmt.Sprintf("%v-%s-%s", v.Number, v.Series, v.Arch)
    89  }
    90  
    91  // GetBSON turns v into a bson.Getter so it can be saved directly
    92  // on a MongoDB database with mgo.
    93  func (v Binary) GetBSON() (interface{}, error) {
    94  	return v.String(), nil
    95  }
    96  
    97  // SetBSON turns v into a bson.Setter so it can be loaded directly
    98  // from a MongoDB database with mgo.
    99  func (vp *Binary) SetBSON(raw bson.Raw) error {
   100  	var s string
   101  	err := raw.Unmarshal(&s)
   102  	if err != nil {
   103  		return err
   104  	}
   105  	v, err := ParseBinary(s)
   106  	if err != nil {
   107  		return err
   108  	}
   109  	*vp = v
   110  	return nil
   111  }
   112  
   113  func (v Binary) MarshalJSON() ([]byte, error) {
   114  	return json.Marshal(v.String())
   115  }
   116  
   117  func (vp *Binary) UnmarshalJSON(data []byte) error {
   118  	var s string
   119  	if err := json.Unmarshal(data, &s); err != nil {
   120  		return err
   121  	}
   122  	v, err := ParseBinary(s)
   123  	if err != nil {
   124  		return err
   125  	}
   126  	*vp = v
   127  	return nil
   128  }
   129  
   130  // GetYAML implements goyaml.Getter
   131  func (v Binary) GetYAML() (tag string, value interface{}) {
   132  	return "", v.String()
   133  }
   134  
   135  // SetYAML implements goyaml.Setter
   136  func (vp *Binary) SetYAML(tag string, value interface{}) bool {
   137  	vstr := fmt.Sprintf("%v", value)
   138  	if vstr == "" {
   139  		return false
   140  	}
   141  	v, err := ParseBinary(vstr)
   142  	if err != nil {
   143  		return false
   144  	}
   145  	*vp = v
   146  	return true
   147  }
   148  
   149  var (
   150  	binaryPat = regexp.MustCompile(`^(\d{1,9})\.(\d{1,9})(\.|-(\w+))(\d{1,9})(\.\d{1,9})?-([^-]+)-([^-]+)$`)
   151  	numberPat = regexp.MustCompile(`^(\d{1,9})\.(\d{1,9})(\.|-(\w+))(\d{1,9})(\.\d{1,9})?$`)
   152  )
   153  
   154  // MustParse parses a version and panics if it does
   155  // not parse correctly.
   156  func MustParse(s string) Number {
   157  	v, err := Parse(s)
   158  	if err != nil {
   159  		panic(err)
   160  	}
   161  	return v
   162  }
   163  
   164  // MustParseBinary parses a binary version and panics if it does
   165  // not parse correctly.
   166  func MustParseBinary(s string) Binary {
   167  	v, err := ParseBinary(s)
   168  	if err != nil {
   169  		panic(err)
   170  	}
   171  	return v
   172  }
   173  
   174  // ParseBinary parses a binary version of the form "1.2.3-series-arch".
   175  func ParseBinary(s string) (Binary, error) {
   176  	m := binaryPat.FindStringSubmatch(s)
   177  	if m == nil {
   178  		return Binary{}, fmt.Errorf("invalid binary version %q", s)
   179  	}
   180  	var v Binary
   181  	v.Major = atoi(m[1])
   182  	v.Minor = atoi(m[2])
   183  	v.Tag = m[4]
   184  	v.Patch = atoi(m[5])
   185  	if m[6] != "" {
   186  		v.Build = atoi(m[6][1:])
   187  	}
   188  	v.Series = m[7]
   189  	v.Arch = m[8]
   190  	_, err := series.GetOSFromSeries(v.Series)
   191  	return v, err
   192  }
   193  
   194  // Parse parses the version, which is of the form 1.2.3
   195  // giving the major, minor and release versions
   196  // respectively.
   197  func Parse(s string) (Number, error) {
   198  	m := numberPat.FindStringSubmatch(s)
   199  	if m == nil {
   200  		return Number{}, fmt.Errorf("invalid version %q", s)
   201  	}
   202  	var v Number
   203  	v.Major = atoi(m[1])
   204  	v.Minor = atoi(m[2])
   205  	v.Tag = m[4]
   206  	v.Patch = atoi(m[5])
   207  	if m[6] != "" {
   208  		v.Build = atoi(m[6][1:])
   209  	}
   210  	return v, nil
   211  }
   212  
   213  // atoi is the same as strconv.Atoi but assumes that
   214  // the string has been verified to be a valid integer.
   215  func atoi(s string) int {
   216  	n, err := strconv.Atoi(s)
   217  	if err != nil {
   218  		panic(err)
   219  	}
   220  	return n
   221  }
   222  
   223  func (v Number) String() string {
   224  	var s string
   225  	if v.Tag == "" {
   226  		s = fmt.Sprintf("%d.%d.%d", v.Major, v.Minor, v.Patch)
   227  	} else {
   228  		s = fmt.Sprintf("%d.%d-%s%d", v.Major, v.Minor, v.Tag, v.Patch)
   229  	}
   230  	if v.Build > 0 {
   231  		s += fmt.Sprintf(".%d", v.Build)
   232  	}
   233  	return s
   234  }
   235  
   236  // Compare returns -1, 0 or 1 depending on whether
   237  // v is less than, equal to or greater than w.
   238  func (v Number) Compare(w Number) int {
   239  	if v == w {
   240  		return 0
   241  	}
   242  	less := false
   243  	switch {
   244  	case v.Major != w.Major:
   245  		less = v.Major < w.Major
   246  	case v.Minor != w.Minor:
   247  		less = v.Minor < w.Minor
   248  	case v.Tag != w.Tag:
   249  		switch {
   250  		case v.Tag == "":
   251  			less = false
   252  		case w.Tag == "":
   253  			less = true
   254  		default:
   255  			less = v.Tag < w.Tag
   256  		}
   257  	case v.Patch != w.Patch:
   258  		less = v.Patch < w.Patch
   259  	case v.Build != w.Build:
   260  		less = v.Build < w.Build
   261  	}
   262  	if less {
   263  		return -1
   264  	}
   265  	return 1
   266  }
   267  
   268  // GetBSON turns v into a bson.Getter so it can be saved directly
   269  // on a MongoDB database with mgo.
   270  func (v Number) GetBSON() (interface{}, error) {
   271  	return v.String(), nil
   272  }
   273  
   274  // SetBSON turns v into a bson.Setter so it can be loaded directly
   275  // from a MongoDB database with mgo.
   276  func (vp *Number) SetBSON(raw bson.Raw) error {
   277  	var s string
   278  	err := raw.Unmarshal(&s)
   279  	if err != nil {
   280  		return err
   281  	}
   282  	v, err := Parse(s)
   283  	if err != nil {
   284  		return err
   285  	}
   286  	*vp = v
   287  	return nil
   288  }
   289  
   290  func (v Number) MarshalJSON() ([]byte, error) {
   291  	return json.Marshal(v.String())
   292  }
   293  
   294  func (vp *Number) UnmarshalJSON(data []byte) error {
   295  	var s string
   296  	if err := json.Unmarshal(data, &s); err != nil {
   297  		return err
   298  	}
   299  	v, err := Parse(s)
   300  	if err != nil {
   301  		return err
   302  	}
   303  	*vp = v
   304  	return nil
   305  }
   306  
   307  // GetYAML implements goyaml.Getter
   308  func (v Number) GetYAML() (tag string, value interface{}) {
   309  	return "", v.String()
   310  }
   311  
   312  // SetYAML implements goyaml.Setter
   313  func (vp *Number) SetYAML(tag string, value interface{}) bool {
   314  	vstr := fmt.Sprintf("%v", value)
   315  	if vstr == "" {
   316  		return false
   317  	}
   318  	v, err := Parse(vstr)
   319  	if err != nil {
   320  		return false
   321  	}
   322  	*vp = v
   323  	return true
   324  }
   325  
   326  func isOdd(x int) bool {
   327  	return x%2 != 0
   328  }
   329  
   330  // IsDev returns whether the version represents a development version. A
   331  // version with a tag or a nonzero build component is considered to be a
   332  // development version.  Versions older than or equal to 1.19.3 (the switch
   333  // over time) check for odd minor versions.
   334  func (v Number) IsDev() bool {
   335  	if v.Compare(switchOverVersion) <= 0 {
   336  		return isOdd(v.Minor) || v.Build > 0
   337  	}
   338  	return v.Tag != "" || v.Build > 0
   339  }
   340  
   341  // ParseMajorMinor takes an argument of the form "major.minor" and returns ints major and minor.
   342  func ParseMajorMinor(vers string) (int, int, error) {
   343  	parts := strings.Split(vers, ".")
   344  	major, err := strconv.Atoi(parts[0])
   345  	minor := -1
   346  	if err != nil {
   347  		return -1, -1, fmt.Errorf("invalid major version number %s: %v", parts[0], err)
   348  	}
   349  	if len(parts) == 2 {
   350  		minor, err = strconv.Atoi(parts[1])
   351  		if err != nil {
   352  			return -1, -1, fmt.Errorf("invalid minor version number %s: %v", parts[1], err)
   353  		}
   354  	} else if len(parts) > 2 {
   355  		return -1, -1, fmt.Errorf("invalid major.minor version number %s", vers)
   356  	}
   357  	return major, minor, nil
   358  }