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  }