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 }