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