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