Permalink
Switch branches/tags
Find file
Fetching contributors…
Cannot retrieve contributors at this time
172 lines (152 sloc) 4.65 KB
// Copyright 2015 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.
package service
import (
"fmt"
"strings"
"github.com/juju/errors"
"github.com/juju/utils/featureflag"
"github.com/juju/utils/os"
"github.com/juju/utils/series"
"github.com/juju/utils/shell"
"github.com/juju/juju/feature"
"github.com/juju/juju/service/common"
"github.com/juju/juju/service/systemd"
"github.com/juju/juju/service/upstart"
"github.com/juju/juju/service/windows"
)
// DiscoverService returns an interface to a service appropriate
// for the current system
func DiscoverService(name string, conf common.Conf) (Service, error) {
hostSeries := series.MustHostSeries()
initName, err := discoverInitSystem(hostSeries)
if err != nil {
return nil, errors.Trace(err)
}
service, err := newService(name, conf, initName, hostSeries)
if err != nil {
return nil, errors.Trace(err)
}
return service, nil
}
func discoverInitSystem(hostSeries string) (string, error) {
initName, err := discoverLocalInitSystem()
if errors.IsNotFound(err) {
// Fall back to checking the juju version.
versionInitName, err2 := VersionInitSystem(hostSeries)
if err2 != nil {
// The key error is the one from discoverLocalInitSystem so
// that is what we return.
return "", errors.Wrap(err2, err)
}
initName = versionInitName
} else if err != nil {
return "", errors.Trace(err)
}
return initName, nil
}
// VersionInitSystem returns an init system name based on the provided
// series. If one cannot be identified a NotFound error is returned.
func VersionInitSystem(series string) (string, error) {
initName, err := versionInitSystem(series)
if err != nil {
return "", errors.Trace(err)
}
logger.Debugf("discovered init system %q from series %q", initName, series)
return initName, nil
}
func versionInitSystem(ser string) (string, error) {
seriesos, err := series.GetOSFromSeries(ser)
if err != nil {
notFound := errors.NotFoundf("init system for series %q", ser)
return "", errors.Wrap(err, notFound)
}
switch seriesos {
case os.Windows:
return InitSystemWindows, nil
case os.Ubuntu:
switch ser {
case "precise", "quantal", "raring", "saucy", "trusty", "utopic":
return InitSystemUpstart, nil
default:
// vivid and later
if featureflag.Enabled(feature.LegacyUpstart) {
return InitSystemUpstart, nil
}
return InitSystemSystemd, nil
}
case os.CentOS:
return InitSystemSystemd, nil
}
return "", errors.NotFoundf("unknown os %q (from series %q), init system", seriesos, ser)
}
type discoveryCheck struct {
name string
isRunning func() (bool, error)
}
var discoveryFuncs = []discoveryCheck{
{InitSystemUpstart, upstart.IsRunning},
{InitSystemSystemd, systemd.IsRunning},
{InitSystemWindows, windows.IsRunning},
}
func discoverLocalInitSystem() (string, error) {
for _, check := range discoveryFuncs {
local, err := check.isRunning()
if err != nil {
logger.Debugf("failed to find init system %q: %v", check.name, err)
}
// We expect that in error cases "local" will be false.
if local {
logger.Debugf("discovered init system %q from local host", check.name)
return check.name, nil
}
}
return "", errors.NotFoundf("init system (based on local host)")
}
const discoverInitSystemScript = `
# Use guaranteed discovery mechanisms for known init systems.
if [ -d /run/systemd/system ]; then
echo -n systemd
exit 0
elif [ -f /sbin/initctl ] && /sbin/initctl --system list 2>&1 > /dev/null; then
echo -n upstart
exit 0
fi
# uh-oh
exit 1
`
// DiscoverInitSystemScript returns the shell script to use when
// discovering the local init system. The script is quite specific to
// bash, so it includes an explicit bash shbang.
func DiscoverInitSystemScript() string {
renderer := shell.BashRenderer{}
data := renderer.RenderScript([]string{discoverInitSystemScript})
return string(data)
}
// shellCase is the template for a bash case statement, for use in
// newShellSelectCommand.
const shellCase = `
case "$%s" in
%s
*)
%s
;;
esac`
// newShellSelectCommand creates a bash case statement with clause for
// each of the linux init systems. The body of each clause comes from
// calling the provided handler with the init system name. If the
// handler does not support the args then it returns a false "ok" value.
func newShellSelectCommand(envVarName, dflt string, handler func(string) (string, bool)) string {
var cases []string
for _, initSystem := range linuxInitSystems {
cmd, ok := handler(initSystem)
if !ok {
continue
}
cases = append(cases, initSystem+")", " "+cmd, " ;;")
}
if len(cases) == 0 {
return ""
}
return fmt.Sprintf(shellCase[1:], envVarName, strings.Join(cases, "\n"), dflt)
}