github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/service/discovery.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package service 5 6 import ( 7 "fmt" 8 "strings" 9 10 "github.com/juju/errors" 11 "github.com/juju/os" 12 "github.com/juju/os/series" 13 "github.com/juju/utils/featureflag" 14 "github.com/juju/utils/shell" 15 16 "github.com/juju/juju/feature" 17 "github.com/juju/juju/service/common" 18 "github.com/juju/juju/service/snap" 19 "github.com/juju/juju/service/systemd" 20 "github.com/juju/juju/service/upstart" 21 "github.com/juju/juju/service/windows" 22 ) 23 24 // DiscoverService returns an interface to a service appropriate 25 // for the current system 26 func DiscoverService(name string, conf common.Conf) (Service, error) { 27 hostSeries := series.MustHostSeries() 28 initName, err := discoverInitSystem(hostSeries) 29 if err != nil { 30 return nil, errors.Trace(err) 31 } 32 33 service, err := newService(name, conf, initName, hostSeries) 34 if err != nil { 35 return nil, errors.Trace(err) 36 } 37 38 return service, nil 39 } 40 41 func discoverInitSystem(hostSeries string) (string, error) { 42 initName, err := discoverLocalInitSystem() 43 if errors.IsNotFound(err) { 44 // Fall back to checking the juju version. 45 versionInitName, err2 := VersionInitSystem(hostSeries) 46 if err2 != nil { 47 // The key error is the one from discoverLocalInitSystem so 48 // that is what we return. 49 return "", errors.Wrap(err2, err) 50 } 51 initName = versionInitName 52 } else if err != nil { 53 return "", errors.Trace(err) 54 } 55 return initName, nil 56 } 57 58 // VersionInitSystem returns an init system name based on the provided 59 // series. If one cannot be identified a NotFound error is returned. 60 func VersionInitSystem(series string) (string, error) { 61 initName, err := versionInitSystem(series) 62 if err != nil { 63 return "", errors.Trace(err) 64 } 65 logger.Debugf("discovered init system %q from series %q", initName, series) 66 return initName, nil 67 } 68 69 func versionInitSystem(ser string) (string, error) { 70 seriesos, err := series.GetOSFromSeries(ser) 71 if err != nil { 72 notFound := errors.NotFoundf("init system for series %q", ser) 73 return "", errors.Wrap(err, notFound) 74 } 75 76 switch seriesos { 77 case os.Windows: 78 return InitSystemWindows, nil 79 case os.Ubuntu: 80 switch ser { 81 case "precise", "quantal", "raring", "saucy", "trusty", "utopic": 82 return InitSystemUpstart, nil 83 default: 84 // vivid and later 85 if featureflag.Enabled(feature.LegacyUpstart) { 86 return InitSystemUpstart, nil 87 } 88 return InitSystemSystemd, nil 89 } 90 case os.CentOS: 91 return InitSystemSystemd, nil 92 case os.OpenSUSE: 93 return InitSystemSystemd, nil 94 } 95 return "", errors.NotFoundf("unknown os %q (from series %q), init system", seriesos, ser) 96 } 97 98 type discoveryCheck struct { 99 name string 100 isRunning func() (bool, error) 101 } 102 103 var discoveryFuncs = []discoveryCheck{ 104 {InitSystemUpstart, upstart.IsRunning}, 105 {InitSystemSystemd, func() (bool, error) { return systemd.IsRunning(), nil }}, 106 {InitSystemWindows, windows.IsRunning}, 107 } 108 109 func discoverLocalInitSystem() (string, error) { 110 if featureflag.Enabled(feature.MongoDbSnap) { 111 local, err := snap.IsRunning() 112 if err == nil && local { 113 return InitSystemSnap, nil 114 } 115 return "", errors.NotFoundf("snap does not appear to be installed correctly") 116 } 117 for _, check := range discoveryFuncs { 118 local, err := check.isRunning() 119 if err != nil { 120 logger.Debugf("failed to find init system %q: %v", check.name, err) 121 } 122 // We expect that in error cases "local" will be false. 123 if local { 124 logger.Debugf("discovered init system %q from local host", check.name) 125 return check.name, nil 126 } 127 } 128 return "", errors.NotFoundf("init system (based on local host)") 129 } 130 131 const ( 132 discoverSnap = "if [ -x /usr/bin/snap ] || [ -d /snap ]; then echo -n snap; exit 0; fi" 133 discoverSystemd = "if [ -d /run/systemd/system ]; then echo -n systemd; exit 0; fi" 134 discoverUpstart = "if [ -f /sbin/initctl ] && /sbin/initctl --system list 2>&1 > /dev/null; then echo -n upstart; exit 0; fi" 135 ) 136 137 // DiscoverInitSystemScript returns the shell script to use when 138 // discovering the local init system. The script is quite specific to 139 // bash, so it includes an explicit bash shbang. 140 func DiscoverInitSystemScript() string { 141 renderer := shell.BashRenderer{} 142 tests := []string{ 143 discoverSystemd, 144 discoverUpstart, 145 "exit 1", 146 } 147 if featureflag.Enabled(feature.MongoDbSnap) { 148 tests = append([]string{discoverSnap}, tests...) 149 } 150 data := renderer.RenderScript(tests) 151 return string(data) 152 } 153 154 // newShellSelectCommand creates a bash case statement with clause for 155 // each of the linux init systems. The body of each clause comes from 156 // calling the provided handler with the init system name. If the 157 // handler does not support the args then it returns a false "ok" value. 158 func newShellSelectCommand(envVarName, defaultCase string, handler func(string) (string, bool)) string { 159 var cases []string 160 161 const shellCaseStatement = ` 162 case "$%s" in 163 %s 164 *) 165 %s 166 ;; 167 esac` 168 169 for _, initSystem := range linuxInitSystems { 170 cmd, ok := handler(initSystem) 171 if !ok { 172 continue 173 } 174 cases = append(cases, initSystem+")", " "+cmd, " ;;") 175 } 176 if len(cases) == 0 { 177 return "" 178 } 179 180 return fmt.Sprintf(shellCaseStatement[1:], envVarName, strings.Join(cases, "\n"), defaultCase) 181 }