Commit e3d5f3e6 authored by Frank Denis's avatar Frank Denis

Update deps

parent 7734cc9d
......@@ -164,11 +164,11 @@
[[projects]]
branch = "master"
digest = "1:2c4470d47613daac5ff660ff9b67a6f8dca489845bb867186e3fcda742a3e53f"
digest = "1:7df4ff301fb9872b59f4d4944ab7c6e3c4cd4076906f499f83759a69df8a7266"
name = "github.com/kardianos/service"
packages = ["."]
pruneopts = "UT"
revision = "615a14ed75099c9eaac6949e22ac2341bf9d3197"
revision = "45244176fc183034f15c4f1c2f801da5e25828b7"
[[projects]]
digest = "1:463e4140189f8194f9121ca1c7fe3b8e9e9a2ab3d949b43c835c21034927dc62"
......@@ -193,11 +193,11 @@
"salsa20/salsa",
]
pruneopts = "UT"
revision = "de0752318171da717af4ce24d0a2e8626afaeb11"
revision = "614d502a4dac94afa3a6ce146bd1736da82514c6"
[[projects]]
branch = "master"
digest = "1:b6a4877dbd8d954aebfe3c6e4b8f674f0564aaf7ac2bb99ea1d9e190410f46c4"
digest = "1:b466f4983ef526ec64fc233af86644596b2c1c5711a7c3b460d908f376f4c4ae"
name = "golang.org/x/net"
packages = [
"bpf",
......@@ -213,11 +213,11 @@
"proxy",
]
pruneopts = "UT"
revision = "f9ce57c11b242f0f1599cf25c89d8cb02c45295a"
revision = "922f4815f713f213882e8ef45e0d315b164d705c"
[[projects]]
branch = "master"
digest = "1:86c7b355731b7a7eb6cb38f1af9c7e025412c081ba76bffed9ba5381802d275a"
digest = "1:8a2bf4e954e1e521d7670730fd03cc8e9e3323d5f29a14c202c1360c5d14cb6f"
name = "golang.org/x/sys"
packages = [
"cpu",
......@@ -229,7 +229,7 @@
"windows/svc/mgr",
]
pruneopts = "UT"
revision = "904bdc257025c7b3f43c19360ad3ab85783fad78"
revision = "3b58ed4ad3395d483fc92d5d14123ce2c3581fec"
[[projects]]
digest = "1:a2ab62866c75542dd18d2b069fec854577a20211d7c0ea6ae746072a1dccdd18"
......
......@@ -75,10 +75,27 @@ const (
optionUserServiceDefault = false
optionSessionCreate = "SessionCreate"
optionSessionCreateDefault = false
optionLogOutput = "LogOutput"
optionLogOutputDefault = false
optionRunWait = "RunWait"
optionReloadSignal = "ReloadSignal"
optionPIDFile = "PIDFile"
optionSystemdScript = "SystemdScript"
optionSysvScript = "SysvScript"
optionUpstartScript = "UpstartScript"
optionLaunchdConfig = "LaunchdConfig"
)
// Status represents service status as an byte value
type Status byte
// Status of service represented as an byte
const (
StatusUnknown Status = iota // Status is unable to be determined due to an error or it was not installed.
StatusRunning
StatusStopped
)
// Config provides the setup for a Service. The Name field is required.
......@@ -103,14 +120,19 @@ type Config struct {
// System specific options.
// * OS X
// - KeepAlive bool (true)
// - RunAtLoad bool (false)
// - UserService bool (false) - Install as a current user service.
// - SessionCreate bool (false) - Create a full user session.
// - LaunchdConfig string () - Use custom launchd config
// - KeepAlive bool (true)
// - RunAtLoad bool (false)
// - UserService bool (false) - Install as a current user service.
// - SessionCreate bool (false) - Create a full user session.
// * POSIX
// - RunWait func() (wait for SIGNAL) - Do not install signal but wait for this function to return.
// - ReloadSignal string () [USR1, ...] - Signal to send on reaload.
// - PIDFile string () [/run/prog.pid] - Location of the PID file.
// - SystemdScript string () - Use custom systemd script
// - UpstartScript string () - Use custom upstart script
// - SysvScript string () - Use custom sysv script
// - RunWait func() (wait for SIGNAL) - Do not install signal but wait for this function to return.
// - ReloadSignal string () [USR1, ...] - Signal to send on reaload.
// - PIDFile string () [/run/prog.pid] - Location of the PID file.
// - LogOutput bool (false) - Redirect StdErr & StdOut to files.
Option KeyValue
}
......@@ -120,10 +142,12 @@ var (
)
var (
// ErrNameFieldRequired is returned when Conifg.Name is empty.
// ErrNameFieldRequired is returned when Config.Name is empty.
ErrNameFieldRequired = errors.New("Config.Name field is required.")
// ErrNoServiceSystemDetected is returned when no system was detected.
ErrNoServiceSystemDetected = errors.New("No service system detected.")
// ErrNotInstalled is returned when the service is not installed
ErrNotInstalled = errors.New("the service is not installed")
)
// New creates a new service based on a service interface and configuration.
......@@ -322,6 +346,9 @@ type Service interface {
// String displays the name of the service. The display name if present,
// otherwise the name.
String() string
// Status returns the current service status.
Status() (Status, error)
}
// ControlAction list valid string texts to use in Control.
......
......@@ -11,6 +11,8 @@ import (
"os/signal"
"os/user"
"path/filepath"
"regexp"
"strings"
"syscall"
"text/template"
"time"
......@@ -100,6 +102,25 @@ func (s *darwinLaunchdService) getServiceFilePath() (string, error) {
return "/Library/LaunchDaemons/" + s.Name + ".plist", nil
}
func (s *darwinLaunchdService) template() *template.Template {
functions := template.FuncMap{
"bool": func(v bool) string {
if v {
return "true"
}
return "false"
},
}
customConfig := s.Option.string(optionLaunchdConfig, "")
if customConfig != "" {
return template.Must(template.New("").Funcs(functions).Parse(customConfig))
} else {
return template.Must(template.New("").Funcs(functions).Parse(launchdConfig))
}
}
func (s *darwinLaunchdService) Install() error {
confPath, err := s.getServiceFilePath()
if err != nil {
......@@ -143,16 +164,7 @@ func (s *darwinLaunchdService) Install() error {
SessionCreate: s.Option.bool(optionSessionCreate, optionSessionCreateDefault),
}
functions := template.FuncMap{
"bool": func(v bool) string {
if v {
return "true"
}
return "false"
},
}
t := template.Must(template.New("launchdConfig").Funcs(functions).Parse(launchdConfig))
return t.Execute(f, to)
return s.template().Execute(f, to)
}
func (s *darwinLaunchdService) Uninstall() error {
......@@ -165,6 +177,32 @@ func (s *darwinLaunchdService) Uninstall() error {
return os.Remove(confPath)
}
func (s *darwinLaunchdService) Status() (Status, error) {
exitCode, out, err := runWithOutput("launchctl", "list", s.Name)
if exitCode == 0 && err != nil {
if !strings.Contains(err.Error(), "failed with stderr") {
return StatusUnknown, err
}
}
re := regexp.MustCompile(`"PID" = ([0-9]+);`)
matches := re.FindStringSubmatch(out)
if len(matches) == 2 {
return StatusRunning, nil
}
confPath, err := s.getServiceFilePath()
if err != nil {
return StatusUnknown, err
}
if _, err = os.Stat(confPath); err == nil {
return StatusStopped, nil
}
return StatusUnknown, ErrNotInstalled
}
func (s *darwinLaunchdService) Start() error {
confPath, err := s.getServiceFilePath()
if err != nil {
......
......@@ -9,6 +9,9 @@ import (
"fmt"
"os"
"os/signal"
"regexp"
"strconv"
"strings"
"syscall"
"text/template"
)
......@@ -52,8 +55,49 @@ func (s *systemd) configPath() (cp string, err error) {
cp = "/etc/systemd/system/" + s.Config.Name + ".service"
return
}
func (s *systemd) getSystemdVersion() int64 {
_, out, err := runWithOutput("systemctl", "--version")
if err != nil {
return -1
}
re := regexp.MustCompile(`systemd ([0-9]+)`)
matches := re.FindStringSubmatch(out)
if len(matches) != 2 {
return -1
}
v, err := strconv.ParseInt(matches[1], 10, 64)
if err != nil {
return -1
}
return v
}
func (s *systemd) hasOutputFileSupport() bool {
defaultValue := true
version := s.getSystemdVersion()
if version == -1 {
return defaultValue
}
if version < 236 {
return false
}
return defaultValue
}
func (s *systemd) template() *template.Template {
return template.Must(template.New("").Funcs(tf).Parse(systemdScript))
customScript := s.Option.string(optionSystemdScript, "")
if customScript != "" {
return template.Must(template.New("").Funcs(tf).Parse(customScript))
} else {
return template.Must(template.New("").Funcs(tf).Parse(systemdScript))
}
}
func (s *systemd) Install() error {
......@@ -79,14 +123,18 @@ func (s *systemd) Install() error {
var to = &struct {
*Config
Path string
ReloadSignal string
PIDFile string
Path string
HasOutputFileSupport bool
ReloadSignal string
PIDFile string
LogOutput bool
}{
s.Config,
path,
s.hasOutputFileSupport(),
s.Option.string(optionReloadSignal, ""),
s.Option.string(optionPIDFile, ""),
s.Option.bool(optionLogOutput, optionLogOutputDefault),
}
err = s.template().Execute(f, to)
......@@ -141,6 +189,24 @@ func (s *systemd) Run() (err error) {
return s.i.Stop(s)
}
func (s *systemd) Status() (Status, error) {
exitCode, out, err := runWithOutput("systemctl", "is-active", s.Name)
if exitCode == 0 && err != nil {
return StatusUnknown, err
}
switch {
case strings.HasPrefix(out, "active"):
return StatusRunning, nil
case strings.HasPrefix(out, "inactive"):
return StatusStopped, nil
case strings.HasPrefix(out, "failed"):
return StatusUnknown, errors.New("service in failed state")
default:
return StatusUnknown, ErrNotInstalled
}
}
func (s *systemd) Start() error {
return run("systemctl", "start", s.Name+".service")
}
......@@ -166,6 +232,10 @@ ExecStart={{.Path|cmdEscape}}{{range .Arguments}} {{.|cmd}}{{end}}
{{if .UserName}}User={{.UserName}}{{end}}
{{if .ReloadSignal}}ExecReload=/bin/kill -{{.ReloadSignal}} "$MAINPID"{{end}}
{{if .PIDFile}}PIDFile={{.PIDFile|cmd}}{{end}}
{{if and .LogOutput .HasOutputFileSupport -}}
StandardOutput=file:/var/log/{{.Name}}.out
StandardError=file:/var/log/{{.Name}}.err
{{- end}}
Restart=always
RestartSec=120
EnvironmentFile=-/etc/sysconfig/{{.Name}}
......
......@@ -9,6 +9,7 @@ import (
"fmt"
"os"
"os/signal"
"strings"
"syscall"
"text/template"
"time"
......@@ -45,8 +46,15 @@ func (s *sysv) configPath() (cp string, err error) {
cp = "/etc/init.d/" + s.Config.Name
return
}
func (s *sysv) template() *template.Template {
return template.Must(template.New("").Funcs(tf).Parse(sysvScript))
customScript := s.Option.string(optionSysvScript, "")
if customScript != "" {
return template.Must(template.New("").Funcs(tf).Parse(customScript))
} else {
return template.Must(template.New("").Funcs(tf).Parse(sysvScript))
}
}
func (s *sysv) Install() error {
......@@ -136,6 +144,22 @@ func (s *sysv) Run() (err error) {
return s.i.Stop(s)
}
func (s *sysv) Status() (Status, error) {
_, out, err := runWithOutput("service", s.Name, "status")
if err != nil {
return StatusUnknown, err
}
switch {
case strings.HasPrefix(out, "Running"):
return StatusRunning, nil
case strings.HasPrefix(out, "Stopped"):
return StatusStopped, nil
default:
return StatusUnknown, ErrNotInstalled
}
}
func (s *sysv) Start() error {
return run("service", s.Name, "start")
}
......
......@@ -8,9 +8,11 @@ package service
import (
"fmt"
"io"
"io/ioutil"
"log/syslog"
"os/exec"
"syscall"
)
func newSysLogger(name string, errs chan<- error) (Logger, error) {
......@@ -53,20 +55,43 @@ func (s sysLogger) Infof(format string, a ...interface{}) error {
}
func run(command string, arguments ...string) error {
_, _, err := runCommand(command, false, arguments...)
return err
}
func runWithOutput(command string, arguments ...string) (int, string, error) {
return runCommand(command, true, arguments...)
}
func runCommand(command string, readStdout bool, arguments ...string) (int, string, error) {
cmd := exec.Command(command, arguments...)
var output string
var stdout io.ReadCloser
var err error
if readStdout {
// Connect pipe to read Stdout
stdout, err = cmd.StdoutPipe()
if err != nil {
// Failed to connect pipe
return 0, "", fmt.Errorf("%q failed to connect stdout pipe: %v", command, err)
}
}
// Connect pipe to read Stderr
stderr, err := cmd.StderrPipe()
if err != nil {
// Failed to connect pipe
return fmt.Errorf("%q failed to connect stderr pipe: %v", command, err)
return 0, "", fmt.Errorf("%q failed to connect stderr pipe: %v", command, err)
}
// Do not use cmd.Run()
if err := cmd.Start(); err != nil {
// Problem while copying stdin, stdout, or stderr
return fmt.Errorf("%q failed: %v", command, err)
return 0, "", fmt.Errorf("%q failed: %v", command, err)
}
// Zero exit status
......@@ -75,14 +100,39 @@ func run(command string, arguments ...string) error {
if command == "launchctl" {
slurp, _ := ioutil.ReadAll(stderr)
if len(slurp) > 0 {
return fmt.Errorf("%q failed with stderr: %s", command, slurp)
return 0, "", fmt.Errorf("%q failed with stderr: %s", command, slurp)
}
}
if readStdout {
out, err := ioutil.ReadAll(stdout)
if err != nil {
return 0, "", fmt.Errorf("%q failed while attempting to read stdout: %v", command, err)
} else if len(out) > 0 {
output = string(out)
}
}
if err := cmd.Wait(); err != nil {
// Command didn't exit with a zero exit status.
return fmt.Errorf("%q failed: %v", command, err)
exitStatus, ok := isExitError(err)
if ok {
// Command didn't exit with a zero exit status.
return exitStatus, output, err
}
// An error occurred and there is no exit status.
return 0, output, fmt.Errorf("%q failed: %v", command, err)
}
return 0, output, nil
}
func isExitError(err error) (int, bool) {
if exiterr, ok := err.(*exec.ExitError); ok {
if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
return status.ExitStatus(), true
}
}
return nil
return 0, false
}
......@@ -8,10 +8,8 @@ import (
"errors"
"fmt"
"os"
"os/exec"
"os/signal"
"regexp"
"strconv"
"strings"
"text/template"
"time"
......@@ -21,6 +19,13 @@ func isUpstart() bool {
if _, err := os.Stat("/sbin/upstart-udev-bridge"); err == nil {
return true
}
if _, err := os.Stat("/sbin/initctl"); err == nil {
if _, out, err := runWithOutput("/sbin/initctl", "--version"); err == nil {
if strings.Contains(out, "initctl (upstart") {
return true
}
}
}
return false
}
......@@ -61,46 +66,57 @@ func (s *upstart) configPath() (cp string, err error) {
func (s *upstart) hasKillStanza() bool {
defaultValue := true
out, err := exec.Command("/sbin/init", "--version").Output()
if err != nil {
version := s.getUpstartVersion()
if version == nil {
return defaultValue
}
re := regexp.MustCompile(`init \(upstart (\d+.\d+.\d+)\)`)
matches := re.FindStringSubmatch(string(out))
if len(matches) != 2 {
return defaultValue
maxVersion := []int{0, 6, 5}
if matches, err := versionAtMost(version, maxVersion); err != nil || matches {
return false
}
version := make([]int, 3)
for idx, vStr := range strings.Split(matches[1], ".") {
version[idx], err = strconv.Atoi(vStr)
if err != nil {
return defaultValue
}
return defaultValue
}
func (s *upstart) hasSetUIDStanza() bool {
defaultValue := true
version := s.getUpstartVersion()
if version == nil {
return defaultValue
}
maxVersion := []int{0, 6, 5}
if versionAtMost(version, maxVersion) {
maxVersion := []int{1, 4, 0}
if matches, err := versionAtMost(version, maxVersion); err != nil || matches {
return false
}
return defaultValue
}
func versionAtMost(version, max []int) bool {
for idx, m := range max {
v := version[idx]
if v > m {
return false
}
func (s *upstart) getUpstartVersion() []int {
_, out, err := runWithOutput("/sbin/initctl", "--version")
if err != nil {
return nil
}
return true
re := regexp.MustCompile(`initctl \(upstart (\d+.\d+.\d+)\)`)
matches := re.FindStringSubmatch(out)
if len(matches) != 2 {
return nil
}
return parseVersion(matches[1])
}
func (s *upstart) template() *template.Template {
return template.Must(template.New("").Funcs(tf).Parse(upstartScript))
customScript := s.Option.string(optionUpstartScript, "")
if customScript != "" {
return template.Must(template.New("").Funcs(tf).Parse(customScript))
} else {
return template.Must(template.New("").Funcs(tf).Parse(upstartScript))
}
}
func (s *upstart) Install() error {
......@@ -126,12 +142,16 @@ func (s *upstart) Install() error {
var to = &struct {
*Config
Path string
HasKillStanza bool
Path string
HasKillStanza bool
HasSetUIDStanza bool
LogOutput bool
}{
s.Config,
path,
s.hasKillStanza(),
s.hasSetUIDStanza(),
s.Option.bool(optionLogOutput, optionLogOutputDefault),
}
return s.template().Execute(f, to)
......@@ -173,6 +193,22 @@ func (s *upstart) Run() (err error) {
return s.i.Stop(s)
}
func (s *upstart) Status() (Status, error) {
exitCode, out, err := runWithOutput("initctl", "status", s.Name)
if exitCode == 0 && err != nil {
return StatusUnknown, err
}
switch {
case strings.HasPrefix(out, fmt.Sprintf("%s start/running", s.Name)):
return StatusRunning, nil
case strings.HasPrefix(out, fmt.Sprintf("%s stop/waiting", s.Name)):
return StatusStopped, nil
default:
return StatusUnknown, ErrNotInstalled
}
}
func (s *upstart) Start() error {
return run("initctl", "start", s.Name)
}
......@@ -202,7 +238,7 @@ const upstartScript = `# {{.Description}}
start on filesystem or runlevel [2345]
stop on runlevel [!2345]
{{if .UserName}}setuid {{.UserName}}{{end}}
{{if and .UserName .HasSetUIDStanza}}setuid {{.UserName}}{{end}}
respawn
respawn limit 10 5
......@@ -215,5 +251,18 @@ pre-start script
end script
# Start
exec {{.Path}}{{range .Arguments}} {{.|cmd}}{{end}}
script
{{if .LogOutput}}
stdout_log="/var/log/{{.Name}}.out"
stderr_log="/var/log/{{.Name}}.err"
{{end}}
if [ -f "/etc/sysconfig/{{.Name}}" ]; then
set -a
source /etc/sysconfig/{{.Name}}
set +a
fi
exec {{if and .UserName (not .HasSetUIDStanza)}}sudo -E -u {{.UserName}} {{end}}{{.Path}}{{range .Arguments}} {{.|cmd}}{{end}}{{if .LogOutput}} >> $stdout_log 2>> $stderr_log{{end}}
end script
`
......@@ -275,6 +275,46 @@ func (ws *windowsService) Run() error {
return ws.i.Stop(ws)
}
func (ws *windowsService) Status() (Status, error) {
m, err := mgr.Connect()
if err != nil {
return StatusUnknown, err
}
defer m.Disconnect()
s, err := m.OpenService(ws.Name)
if err != nil {
if err.Error() == "The specified service does not exist as an installed service." {
return StatusUnknown, ErrNotInstalled
}
return StatusUnknown, err
}
status, err := s.Query()
if err != nil {
return StatusUnknown, err
}
switch status.State {
case svc.StartPending:
fallthrough
case svc.Running:
return StatusRunning, nil
case svc.PausePending:
fallthrough
case svc.Paused:
fallthrough
case svc.ContinuePending:
fallthrough
case svc.StopPending: