github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/mongo/mongodfinder.go (about)

     1  // Copyright 2018 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package mongo
     5  
     6  import (
     7  	"os"
     8  	"os/exec"
     9  	"regexp"
    10  	"strconv"
    11  
    12  	"github.com/juju/errors"
    13  	"github.com/juju/utils/featureflag"
    14  
    15  	"github.com/juju/juju/feature"
    16  )
    17  
    18  // SearchTools represents the OS functionality we need to find the correct MongoDB executable.
    19  // The mock for this (used in testing) is automatically generated by 'go generate' from the following line
    20  //go:generate mockgen -package mongo -destination searchtoolsmock_test.go github.com/juju/juju/mongo SearchTools
    21  type SearchTools interface {
    22  	// GetCommandOutput execs the given command, and returns the CombinedOutput, or any error that occurred.
    23  	GetCommandOutput(name string, arg ...string) (string, error)
    24  
    25  	// Exists just returns if a given path is available (eg os.Stat() has a value)
    26  	Exists(string) bool
    27  }
    28  
    29  // MongodFinder searches expected paths to find a version of Mongo and determine what version it is.
    30  type MongodFinder struct {
    31  	search SearchTools
    32  }
    33  
    34  // NewMongodFinder returns a type that will help search for mongod, using normal OS tools.
    35  func NewMongodFinder() *MongodFinder {
    36  	return &MongodFinder{
    37  		search: OSSearchTools{},
    38  	}
    39  }
    40  
    41  // FindBest tries to find the mongo version that best fits what we want to use.
    42  func (m *MongodFinder) FindBest() (string, Version, error) {
    43  	if featureflag.Enabled(feature.MongoDbSnap) {
    44  		v, err := m.findVersion(JujuDbSnapMongodPath)
    45  		if err != nil {
    46  			return "", Version{}, errors.NotFoundf("%s snap not installed correctly. Executable %s", JujuDbSnap, JujuDbSnapMongodPath)
    47  		}
    48  		return JujuDbSnapMongodPath, v, nil
    49  	}
    50  
    51  	// In Bionic and beyond (and early trusty) we just use the system mongo.
    52  	// We only use the system mongo if it is at least Mongo 3.4
    53  	if m.search.Exists(MongodSystemPath) {
    54  		// We found Mongo in the system directory, check to see if the version is valid
    55  		if v, err := m.findVersion(MongodSystemPath); err != nil {
    56  			logger.Warningf("system mongo %q found, but ignoring error trying to get version: %v",
    57  				MongodSystemPath, err)
    58  		} else if v.NewerThan(minimumSystemMongoVersion) >= 0 {
    59  			// We only support mongo 3.4 and newer from the system
    60  			return MongodSystemPath, v, nil
    61  		}
    62  	}
    63  	// the system mongo is either too old, or not valid, keep trying
    64  	if m.search.Exists(JujuMongod32Path) {
    65  		// juju-mongod32 is available, check its version as well. Mostly just as a reporting convenience
    66  		// Do we want to use it even if we can't deal with --version?
    67  		v, err := m.findVersion(JujuMongod32Path)
    68  		if err != nil {
    69  			logger.Warningf("juju-mongodb3.2 %q found, but ignoring error trying to get version: %v",
    70  				JujuMongod32Path, err)
    71  			v = Mongo32wt
    72  		}
    73  		return JujuMongod32Path, v, nil
    74  	}
    75  	if m.search.Exists(JujuMongod24Path) {
    76  		// juju-mongod is available, check its version as well. Mostly just as a reporting convenience
    77  		if v, err := m.findVersion(JujuMongod24Path); err != nil {
    78  			logger.Warningf("juju-mongodb %q found, but ignoring error trying to get version: %v",
    79  				JujuMongod24Path, err)
    80  		} else {
    81  			return JujuMongod24Path, v, nil
    82  		}
    83  	}
    84  	return "", Version{}, errors.NotFoundf("could not find a viable 'mongod'")
    85  }
    86  
    87  // all mongo versions start with "db version v" and then the version is a X.Y.Z-extra
    88  // we don't really care about the 'extra' portion of it, so we just track the rest.
    89  var mongoVersionRegex = regexp.MustCompile(`^db version v(\d{1,9})\.(\d{1,9})(\..*)?`)
    90  
    91  // ParseMongoVersion parses the output from "mongod --version" and returns a Version struct
    92  func ParseMongoVersion(versionInfo string) (Version, error) {
    93  	m := mongoVersionRegex.FindStringSubmatch(versionInfo)
    94  	if m == nil {
    95  		return Version{}, errors.Errorf("'mongod --version' reported:\n%s", versionInfo)
    96  	}
    97  	if len(m) < 3 {
    98  		return Version{}, errors.Errorf("did not find enough version parts in:\n%s", versionInfo)
    99  	}
   100  	logger.Tracef("got version parts: %#v", m)
   101  	var v Version
   102  	var err error
   103  	// Index '[0]' is the full matched string,
   104  	// [1] is the Major
   105  	// [2] is the Minor
   106  	// [3] is the Patch to the end of the line
   107  	if v.Major, err = strconv.Atoi(m[1]); err != nil {
   108  		return Version{}, errors.Annotatef(err, "invalid major version: %q", versionInfo)
   109  	}
   110  	if v.Minor, err = strconv.Atoi(m[2]); err != nil {
   111  		return Version{}, errors.Annotatef(err, "invalid minor version: %q", versionInfo)
   112  	}
   113  	if len(m) > 3 {
   114  		// strip off the beginning '.', and make sure there is something after it
   115  		tail := m[3]
   116  		if len(tail) > 1 {
   117  			v.Patch = tail[1:]
   118  		}
   119  	}
   120  	return v, nil
   121  }
   122  
   123  func (m *MongodFinder) findVersion(path string) (Version, error) {
   124  	out, err := m.search.GetCommandOutput(path, "--version")
   125  	if err != nil {
   126  		return Version{}, errors.Trace(err)
   127  	}
   128  	v, err := ParseMongoVersion(out)
   129  	if err != nil {
   130  		return Version{}, errors.Trace(err)
   131  	}
   132  	if v.NewerThan(Mongo26) > 0 {
   133  		v.StorageEngine = WiredTiger
   134  	} else {
   135  		v.StorageEngine = MMAPV1
   136  	}
   137  	return v, nil
   138  }
   139  
   140  type OSSearchTools struct{}
   141  
   142  func (OSSearchTools) Exists(name string) bool {
   143  	_, err := os.Stat(name)
   144  	return err == nil
   145  }
   146  
   147  func (OSSearchTools) GetCommandOutput(name string, arg ...string) (string, error) {
   148  	cmd := exec.Command(name, arg...)
   149  	output, err := cmd.CombinedOutput()
   150  	return string(output), errors.Trace(err)
   151  }