service: move to v2 API (beta).

This commit is contained in:
Daniel Theophanes
2015-01-13 09:41:14 -08:00
parent 6d5062006f
commit 8b12f7ccf2
12 changed files with 521 additions and 609 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright (c) 2012 Daniel Theophanes
Copyright (c) 2015 Daniel Theophanes
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
-170
View File
@@ -1,170 +0,0 @@
// A simple and consistent method to extract a configuration from a file.
// This doesn't contain any method to actually decode the file. The actual
// decoding is done in an external function.
package config
import (
"encoding/json"
"io"
"os"
"path/filepath"
"time"
"bitbucket.org/kardianos/osext"
"gopkg.in/fsnotify.v1"
)
const DefaultPostfix = "_config.json"
// Simple JSON based configuration decoder.
func DecodeJsonConfig(r io.Reader, v interface{}) error {
d := json.NewDecoder(r)
return d.Decode(v)
}
// Simple JSON based configuration encoder.
func EncodeJsonConfig(w io.Writer, v interface{}) error {
bb, err := json.MarshalIndent(v, "", "\t")
if err != nil {
return err
}
n, tot := 0, 0
for {
n, err = w.Write(bb[tot:])
tot += n
if len(bb) == tot {
break
}
}
return err
}
// Return a configuration file path. If baseName is empty,
// then the current executable name without the extension
// is used. If postfix is empty then DefaultPostfix is used.
func GetConfigFilePath(baseName, postfix string) (string, error) {
if len(postfix) == 0 {
postfix = DefaultPostfix
}
path, err := osext.Executable()
if err != nil {
return "", err
}
path, exeName := filepath.Split(path)
if len(baseName) == 0 {
exeName = exeName[:len(exeName)-len(filepath.Ext(exeName))]
} else {
exeName = baseName
}
configPath := filepath.Join(path, exeName+postfix)
return configPath, nil
}
type DecodeConfig func(r io.Reader, v interface{}) error
type EncodeConfig func(w io.Writer, v interface{}) error
type WatchConfig struct {
// Notified here if the file changes.
C chan *WatchConfig
filepath string
watch *fsnotify.Watcher
decode DecodeConfig
close chan struct{}
}
// Create a new configuration watcher. Adds a notification if the configuration file changes
// so it may be reloaded. If defaultConfig is not nil and encode is not nil, the
// configuration file path is checked if a file exists or not. If it doesn't exist
// the default configuration is written to the file.
func NewWatchConfig(filepath string, decode DecodeConfig, defaultConfig interface{}, encode EncodeConfig) (*WatchConfig, error) {
if defaultConfig != nil && encode != nil {
f, err := os.Open(filepath)
if f != nil {
f.Close()
}
if os.IsNotExist(err) {
f, err = os.Create(filepath)
if err != nil {
return nil, err
}
err = encode(f, defaultConfig)
f.Close()
if err != nil {
return nil, err
}
}
}
watch, err := fsnotify.NewWatcher()
if err != nil {
return nil, err
}
err = watch.Add(filepath)
if err != nil {
return nil, err
}
wc := &WatchConfig{
C: make(chan *WatchConfig),
filepath: filepath,
watch: watch,
decode: decode,
close: make(chan struct{}, 0),
}
go wc.run()
return wc, nil
}
func (wc *WatchConfig) run() {
// Work around watch events being sent more then once.
ticker := time.NewTicker(time.Second)
trigger := false
waitOnce := false
for {
select {
case <-wc.close:
close(wc.C)
return
case <-wc.watch.Errors:
// Nothing right now.
case <-wc.watch.Events:
trigger = true
case <-ticker.C:
// Think of this as a PLC state machine.
if trigger && waitOnce {
wc.C <- wc
trigger = false
waitOnce = false
}
if trigger && !waitOnce {
waitOnce = true
}
}
}
}
// Send a notification as if the configuration file has changed.
func (wc *WatchConfig) TriggerC() {
wc.C <- wc
}
// Load the configuration from the file into the provided value.
func (wc *WatchConfig) Load(v interface{}) error {
f, err := os.Open(wc.filepath)
if err != nil {
return err
}
defer f.Close()
return wc.decode(f, v)
}
// Stop the watch and any loops that are running.
func (wc *WatchConfig) Close() error {
wc.close <- struct{}{}
return wc.watch.Close()
}
+44
View File
@@ -0,0 +1,44 @@
package service
import (
"log"
"os"
)
// ConsoleLogger logs to the std err.
var ConsoleLogger = consoleLogger{}
type consoleLogger struct {
info, warn, err *log.Logger
}
func init() {
ConsoleLogger.info = log.New(os.Stderr, "I: ", log.Ltime)
ConsoleLogger.warn = log.New(os.Stderr, "W: ", log.Ltime)
ConsoleLogger.err = log.New(os.Stderr, "E: ", log.Ltime)
}
func (c consoleLogger) Error(v ...interface{}) error {
c.err.Print(v...)
return nil
}
func (c consoleLogger) Warning(v ...interface{}) error {
c.warn.Print(v...)
return nil
}
func (c consoleLogger) Info(v ...interface{}) error {
c.info.Print(v...)
return nil
}
func (c consoleLogger) Errorf(format string, a ...interface{}) error {
c.err.Printf(format, a...)
return nil
}
func (c consoleLogger) Warningf(format string, a ...interface{}) error {
c.warn.Printf(format, a...)
return nil
}
func (c consoleLogger) Infof(format string, a ...interface{}) error {
c.info.Printf(format, a...)
return nil
}
BIN
View File
Binary file not shown.
+60 -74
View File
@@ -1,95 +1,81 @@
package main
import (
"fmt"
"log"
"os"
"time"
"bitbucket.org/kardianos/service"
"bitbucket.org/kardianos/service2beta"
)
var log service.Logger
var logger service.Logger
type program struct {
exit chan struct{}
}
func (p *program) Start(s service.Service) error {
if s.Interactive() {
logger.Info("Running in terminal.")
} else {
logger.Info("Running under service manager.")
}
p.exit = make(chan struct{})
go p.run()
return nil
}
func (p *program) run() error {
logger.Infof("I'm running %v.", service.LocalSystem())
ticker := time.NewTicker(2 * time.Second)
for {
select {
case tm := <-ticker.C:
err := logger.Infof("Still running at %v...", tm)
if err != nil {
panic(err)
}
case <-p.exit:
ticker.Stop()
return nil
}
}
return nil
}
func (p *program) Stop(s service.Service) error {
err := logger.Info("I'm Stopping!")
if err != nil {
panic(err)
}
close(p.exit)
return nil
}
func main() {
var name = "GoServiceTest"
var displayName = "Go Service Test"
var desc = "This is a test Go service. It is designed to run well."
var s, err = service.NewService(name, displayName, desc)
log = s
svcConfig := &service.Config{
Name: "GoServiceTest",
DisplayName: "Go Service Test",
Description: "This is a test Go service.",
}
prg := &program{}
s, err := service.New(prg, svcConfig)
if err != nil {
fmt.Printf("%s unable to start: %s", displayName, err)
return
panic(err)
}
logger, err = s.SystemLogger()
if err != nil {
panic(err)
}
if len(os.Args) > 1 {
var err error
verb := os.Args[1]
switch verb {
case "install":
err = s.Install()
if err != nil {
fmt.Printf("Failed to install: %s\n", err)
return
}
fmt.Printf("Service \"%s\" installed.\n", displayName)
case "remove":
err = s.Remove()
if err != nil {
fmt.Printf("Failed to remove: %s\n", err)
return
}
fmt.Printf("Service \"%s\" removed.\n", displayName)
case "run":
doWork()
case "start":
err = s.Start()
if err != nil {
fmt.Printf("Failed to start: %s\n", err)
return
}
fmt.Printf("Service \"%s\" started.\n", displayName)
case "stop":
err = s.Stop()
if err != nil {
fmt.Printf("Failed to stop: %s\n", err)
return
}
fmt.Printf("Service \"%s\" stopped.\n", displayName)
err := service.Control(s, os.Args[1])
if err != nil {
log.Fatal(err)
}
return
}
err = s.Run(func() error {
// start
go doWork()
return nil
}, func() error {
// stop
stopWork()
return nil
})
err = s.Run()
if err != nil {
s.Error(err.Error())
logger.Error(err)
}
}
var exit = make(chan struct{})
func doWork() {
log.Info("I'm Running!")
ticker := time.NewTicker(time.Minute)
for {
select {
case <-ticker.C:
log.Info("Still running...")
case <-exit:
ticker.Stop()
return
}
}
}
func stopWork() {
log.Info("I'm Stopping!")
exit <- struct{}{}
}
+117 -61
View File
@@ -2,23 +2,16 @@
// Currently supports Windows, Linux/(systemd | Upstart | SysV), and OSX/Launchd.
package service
import "bitbucket.org/kardianos/osext"
import (
"errors"
"fmt"
)
// Creates a new service. name is the internal name
// and should not contain spaces. Display name is the pretty print
// name. The description is an arbitrary string used to describe the
// service.
func NewService(name, displayName, description string) (Service, error) {
return newService(&Config{
Name: name,
DisplayName: displayName,
Description: description,
})
}
// Alpha API. Do not yet use.
// Config provides the setup for a Service. The Name field is required.
type Config struct {
Name, DisplayName, Description string
Name string // Required name of the service. No spaces suggested.
DisplayName string // Display name, spaces allowed.
Description string // Long description of service.
UserName string // Run as username.
Arguments []string // Run with arguments.
@@ -32,6 +25,18 @@ type Config struct {
KV KeyValue
}
var errNameFieldRequired = errors.New("Config.Name field is required.")
// New creates a new service based on a service interface and configuration.
func New(i Interface, c *Config) (Service, error) {
if len(c.Name) == 0 {
return nil, errNameFieldRequired
}
return newService(i, c)
}
// KeyValue provides a list of platform specific options. See platform docs for
// more details.
type KeyValue map[string]interface{}
// Bool returns the value of the given name, assuming the value is a boolean.
@@ -78,65 +83,116 @@ func (kv KeyValue) float64(name string, defaultValue float64) float64 {
return defaultValue
}
// Alpha API. Do not yet use.
func NewServiceConfig(c *Config) (Service, error) {
return newService(c)
}
// Represents a generic way to interact with the system's service.
type Service interface {
Installer
Controller
Runner
Logger
// System represents the system and system's service being used.
type System interface {
// String returns a description of the OS and service platform.
String() string
}
// A Generic way to stop and start a service.
type Runner interface {
// Call quickly after initial entry point. Does not return until
// service is ready to stop. onStart is called when the service is
// starting, returning an error will fail to start the service.
// If an error is returned from onStop, the service will still stop.
// An error passed from onStart or onStop will be returned as
// an error from Run.
// Both callbacks should return quickly and not block.
Run(onStart, onStop func() error) error
// LocalSystem get's the local system information.
func LocalSystem() System {
return system
}
// Simple install and remove commands.
type Installer interface {
// Installs this service on the system. May return an
// error if this service is already installed.
Install() error
// Interface represents the service interface for a program. Start runs before
// the hosting process is granted control and Stop runs when control is returned.
//
// 1. OS service manager executes user program.
// 2. User program sees it is executed from a service manager (IsInteractive is false).
// 3. User program calls Service.Run() which blocks.
// 4. Interface.Start() is called and quickly returns.
// 5. User program runs.
// 6. OS service manager signals the user program to stop.
// 7. Interface.Stop() is called and quickly returns.
// - For a successful exit, os.Exit should not be called in Interface.Stop().
// 8. Service.Run returns.
// 9. User program should quickly exit.
type Interface interface {
// Start provides a place to initiate the service. The service doesn't not
// signal a completed start until after this function returns, so the
// Start function must not take more then a few seconds at most.
Start(s Service) error
// Removes this service from the system. May return an
// error if this service is not already installed.
Remove() error
// Stop provides a place to clean up program execution before it is terminated.
// It should not take more then a few seconds to execute.
// Stop should not call os.Exit directly in the function.
Stop(s Service) error
}
// A service that implements ServiceController is able to
// start and stop itself.
type Controller interface {
// Starts this service on the system.
// Service represents a service that can be run or controlled.
type Service interface {
// Run should be called shortly after the program entry point.
// After Interface.Stop has finished running, Run will stop blocking.
// After Run stops blocking, the program must exit shortly after.
Run() error
// Start signals to the OS service manager the given service should start.
Start() error
// Stops this service on the system.
// Stop signals to the OS service manager the given service should stop.
Stop() error
// Restart signals to the OS service manager the given service should stop then start.
Restart() error
// Install setups up the given service in the OS service manager. This may require
// greater rights. Will return an error if it is already installed.
Install() error
// Remove removes the given service from the OS service manager. This may require
// greater rights. Will return an error if the service is not present.
Remove() error
// Interactive returns false if running under the OS service manager
// and true otherwise.
Interactive() bool
// Opens and returns a system logger. If the user program is running
// interactively rather then as a service, the returned logger will write to
// os.Stderr.
Logger() (Logger, error)
// SystemLogger opens and returns a system logger.
SystemLogger() (Logger, error)
// String displays the name of the service. The display name if present,
// otherwise the name.
String() string
}
// A service that implements ServiceLogger can perform simple system logging.
// ControlAction list valid string texts to use in Control.
var ControlAction = [5]string{"start", "stop", "restart", "install", "remove"}
// Control issues control functions to the service from a given action string.
func Control(s Service, action string) error {
var err error
switch action {
case ControlAction[0]:
err = s.Start()
case ControlAction[1]:
err = s.Stop()
case ControlAction[2]:
err = s.Restart()
case ControlAction[3]:
err = s.Install()
case ControlAction[4]:
err = s.Remove()
default:
err = fmt.Errorf("Unknown action %s", action)
}
if err != nil {
return fmt.Errorf("Failed to %s %v: %v", action, s, err)
}
return nil
}
// Logger writes to the system log.
type Logger interface {
// Basic log functions in the context of the service.
Error(format string, a ...interface{}) error
Warning(format string, a ...interface{}) error
Info(format string, a ...interface{}) error
}
Error(v ...interface{}) error
Warning(v ...interface{}) error
Info(v ...interface{}) error
// Depreciated. Use osext.Executable instead.
// Returns the full path of the running executable
// as reported by the system. Includes the executable
// image name.
func GetExePath() (exePath string, err error) {
return osext.Executable()
Errorf(format string, a ...interface{}) error
Warningf(format string, a ...interface{}) error
Infof(format string, a ...interface{}) error
}
+50 -23
View File
@@ -2,40 +2,57 @@ package service
import (
"fmt"
"log/syslog"
"os"
"os/exec"
"os/signal"
"os/user"
"text/template"
"time"
"bitbucket.org/kardianos/osext"
)
const maxPathSize = 32 * 1024
func newService(c *Config) (s *darwinLaunchdService, err error) {
s = &darwinLaunchdService{
const version = "Darwin Launchd"
type darwinSystem struct{}
func (ls darwinSystem) String() string {
return version
}
var system = darwinSystem{}
func isInteractive() (bool, error) {
// TODO: The PPID of Launchd is 1. The PPid of a service process should match launchd's PID.
return os.Getppid() != 1, nil
}
func newService(i Interface, c *Config) (*darwinLaunchdService, error) {
s := &darwinLaunchdService{
i: i,
Config: c,
}
s.logger, err = syslog.New(syslog.LOG_INFO, c.Name)
if err != nil {
return nil, err
}
var err error
s.interactive, err = isInteractive()
return s, nil
return s, err
}
type darwinLaunchdService struct {
i Interface
*Config
logger *syslog.Writer
interactive bool
}
const version = "Darwin Launchd"
func (s *darwinLaunchdService) String() string {
return version
if len(s.DisplayName) > 0 {
return s.DisplayName
}
return s.Name
}
func (s *darwinLaunchdService) getServiceFilePath() (string, error) {
@@ -49,6 +66,9 @@ func (s *darwinLaunchdService) getServiceFilePath() (string, error) {
return "/Library/LaunchDaemons/" + s.Name + ".plist", nil
}
func (s *darwinLaunchdService) Interactive() bool {
return s.interactive
}
func (s *darwinLaunchdService) Install() error {
confPath, err := s.getServiceFilePath()
if err != nil {
@@ -120,11 +140,19 @@ func (s *darwinLaunchdService) Stop() error {
cmd := exec.Command("launchctl", "unload", confPath)
return cmd.Run()
}
func (s *darwinLaunchdService) Restart() error {
err := s.Stop()
if err != nil {
return err
}
time.Sleep(50 * time.Millisecond)
return s.Start()
}
func (s *darwinLaunchdService) Run(onStart, onStop func() error) error {
func (s *darwinLaunchdService) Run() error {
var err error
err = onStart()
err = s.i.Start(s)
if err != nil {
return err
}
@@ -135,18 +163,17 @@ func (s *darwinLaunchdService) Run(onStart, onStop func() error) error {
<-sigChan
return onStop()
return s.i.Stop(s)
}
func (s *darwinLaunchdService) Error(format string, a ...interface{}) error {
return s.logger.Err(fmt.Sprintf(format, a...))
func (s *darwinLaunchdService) Logger() (Logger, error) {
if s.interactive {
return ConsoleLogger, nil
}
return s.SystemLogger()
}
func (s *darwinLaunchdService) Warning(format string, a ...interface{}) error {
return s.logger.Warning(fmt.Sprintf(format, a...))
}
func (s *darwinLaunchdService) Info(format string, a ...interface{}) error {
// On Darwin syslog.log defaults to loggint >= Notice (see /etc/asl.conf).
return s.logger.Notice(fmt.Sprintf(format, a...))
func (s *darwinLaunchdService) SystemLogger() (Logger, error) {
return newSysLogger(s.Name)
}
var launchdConfig = `<?xml version='1.0' encoding='UTF-8'?>
+79 -56
View File
@@ -2,11 +2,11 @@ package service
import (
"fmt"
"log/syslog"
"os"
"os/exec"
"os/signal"
"text/template"
"time"
"bitbucket.org/kardianos/osext"
)
@@ -27,23 +27,6 @@ func getFlavor() initFlavor {
return flavor
}
func newService(c *Config) (Service, error) {
s := &linuxService{
flavor: getFlavor(),
name: c.Name,
displayName: c.DisplayName,
description: c.Description,
}
var err error
s.logger, err = syslog.New(syslog.LOG_INFO, s.name)
if err != nil {
return nil, err
}
return s, nil
}
func isUpstart() bool {
if _, err := os.Stat("/sbin/upstart-udev-bridge"); err == nil {
return true
@@ -59,13 +42,38 @@ func isSystemd() bool {
}
type linuxService struct {
flavor initFlavor
name, displayName, description string
logger *syslog.Writer
i Interface
*Config
interactive bool
}
func (ls *linuxService) String() string {
return fmt.Sprintf("Linux %s", ls.flavor.String())
var flavor = getFlavor()
type linuxSystem struct{}
func (ls linuxSystem) String() string {
return fmt.Sprintf("Linux %s", flavor.String())
}
var system = linuxSystem{}
func newService(i Interface, c *Config) (Service, error) {
s := &linuxService{
i: i,
Config: c,
}
var err error
s.interactive, err = isInteractive()
return s, err
}
func (s *linuxService) String() string {
if len(s.DisplayName) > 0 {
return s.DisplayName
}
return s.Name
}
type initFlavor uint8
@@ -109,8 +117,17 @@ func (f initFlavor) GetTemplate() *template.Template {
return template.Must(template.New(f.String() + "Script").Parse(templ))
}
func isInteractive() (bool, error) {
// TODO: Is this true for user services?
return os.Getppid() != 1, nil
}
func (s *linuxService) Interactive() bool {
return s.interactive
}
func (s *linuxService) Install() error {
confPath := s.flavor.ConfigPath(s.name)
confPath := flavor.ConfigPath(s.Name)
_, err := os.Stat(confPath)
if err == nil {
return fmt.Errorf("Init already exists: %s", confPath)
@@ -132,33 +149,33 @@ func (s *linuxService) Install() error {
Description string
Path string
}{
s.displayName,
s.description,
s.DisplayName,
s.Description,
path,
}
err = s.flavor.GetTemplate().Execute(f, to)
err = flavor.GetTemplate().Execute(f, to)
if err != nil {
return err
}
if s.flavor == initSystemV {
if flavor == initSystemV {
if err = os.Chmod(confPath, 0755); err != nil {
return err
}
for _, i := range [...]string{"2", "3", "4", "5"} {
if err = os.Symlink(confPath, "/etc/rc"+i+".d/S50"+s.name); err != nil {
if err = os.Symlink(confPath, "/etc/rc"+i+".d/S50"+s.Name); err != nil {
continue
}
}
for _, i := range [...]string{"0", "1", "6"} {
if err = os.Symlink(confPath, "/etc/rc"+i+".d/K02"+s.name); err != nil {
if err = os.Symlink(confPath, "/etc/rc"+i+".d/K02"+s.Name); err != nil {
continue
}
}
}
if s.flavor == initSystemd {
if flavor == initSystemd {
return exec.Command("systemctl", "daemon-reload").Run()
}
@@ -166,23 +183,30 @@ func (s *linuxService) Install() error {
}
func (s *linuxService) Remove() error {
if s.flavor == initSystemd {
exec.Command("systemctl", "disable", s.name+".service").Run()
if flavor == initSystemd {
exec.Command("systemctl", "disable", s.Name+".service").Run()
}
if err := os.Remove(s.flavor.ConfigPath(s.name)); err != nil {
if err := os.Remove(flavor.ConfigPath(s.Name)); err != nil {
return err
}
return nil
}
func (s *linuxService) Run(onStart, onStop func() error) (err error) {
err = onStart()
func (s *linuxService) Logger() (Logger, error) {
if s.interactive {
return ConsoleLogger, nil
}
return s.SystemLogger()
}
func (s *linuxService) SystemLogger() (Logger, error) {
return newSysLogger(s.Name)
}
func (s *linuxService) Run() (err error) {
err = s.i.Start(s)
if err != nil {
return err
}
defer func() {
err = onStop()
}()
sigChan := make(chan os.Signal, 3)
@@ -190,39 +214,38 @@ func (s *linuxService) Run(onStart, onStop func() error) (err error) {
<-sigChan
return nil
return s.i.Stop(s)
}
func (s *linuxService) Start() error {
switch s.flavor {
switch flavor {
case initSystemd:
return exec.Command("systemctl", "start", s.name+".service").Run()
return exec.Command("systemctl", "start", s.Name+".service").Run()
case initUpstart:
return exec.Command("initctl", "start", s.name).Run()
return exec.Command("initctl", "start", s.Name).Run()
default:
return exec.Command("service", s.name, "start").Run()
return exec.Command("service", s.Name, "start").Run()
}
}
func (s *linuxService) Stop() error {
switch s.flavor {
switch flavor {
case initSystemd:
return exec.Command("systemctl", "stop", s.name+".service").Run()
return exec.Command("systemctl", "stop", s.Name+".service").Run()
case initUpstart:
return exec.Command("initctl", "stop", s.name).Run()
return exec.Command("initctl", "stop", s.Name).Run()
default:
return exec.Command("service", s.name, "stop").Run()
return exec.Command("service", s.Name, "stop").Run()
}
}
func (s *linuxService) Error(format string, a ...interface{}) error {
return s.logger.Err(fmt.Sprintf(format, a...))
}
func (s *linuxService) Warning(format string, a ...interface{}) error {
return s.logger.Warning(fmt.Sprintf(format, a...))
}
func (s *linuxService) Info(format string, a ...interface{}) error {
return s.logger.Info(fmt.Sprintf(format, a...))
func (s *linuxService) Restart() error {
err := s.Stop()
if err != nil {
return err
}
time.Sleep(50 * time.Millisecond)
return s.Start()
}
const systemVScript = `#!/bin/sh
+41
View File
@@ -0,0 +1,41 @@
//
// +build linux darwin
package service
import (
"fmt"
"log/syslog"
)
func newSysLogger(name string) (Logger, error) {
w, err := syslog.New(syslog.LOG_INFO, name)
if err != nil {
return nil, err
}
return sysLogger{w}, nil
}
type sysLogger struct {
*syslog.Writer
}
func (s sysLogger) Error(v ...interface{}) error {
return s.Writer.Err(fmt.Sprint(v...))
}
func (s sysLogger) Warning(v ...interface{}) error {
return s.Writer.Warning(fmt.Sprint(v...))
}
func (s sysLogger) Info(v ...interface{}) error {
return s.Writer.Info(fmt.Sprint(v...))
}
func (s sysLogger) Errorf(format string, a ...interface{}) error {
return s.Writer.Err(fmt.Sprintf(format, a...))
}
func (s sysLogger) Warningf(format string, a ...interface{}) error {
return s.Writer.Warning(fmt.Sprintf(format, a...))
}
func (s sysLogger) Infof(format string, a ...interface{}) error {
return s.Writer.Info(fmt.Sprintf(format, a...))
}
+129 -53
View File
@@ -2,6 +2,9 @@ package service
import (
"fmt"
"os"
"os/signal"
"time"
"bitbucket.org/kardianos/osext"
"code.google.com/p/winsvc/eventlog"
@@ -9,32 +12,94 @@ import (
"code.google.com/p/winsvc/svc"
)
func newService(c *Config) (*windowsService, error) {
return &windowsService{
name: c.Name,
displayName: c.DisplayName,
description: c.Description,
}, nil
}
type windowsService struct {
name, displayName, description string
onStart, onStop func() error
logger *eventlog.Log
}
const version = "Windows Service"
func (ws *windowsService) String() string {
type windowsService struct {
i Interface
*Config
interactive bool
}
// WindowsLogger allows using windows specific logging methods.
type WindowsLogger struct {
ev *eventlog.Log
}
type windowsSystem struct{}
func (windowsSystem) String() string {
return version
}
func (ws *windowsService) Execute(args []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (ssec bool, errno uint32) {
var system = windowsSystem{}
func (l WindowsLogger) Error(v ...interface{}) error {
return l.ev.Error(3, fmt.Sprint(v...))
}
func (l WindowsLogger) Warning(v ...interface{}) error {
return l.ev.Warning(2, fmt.Sprint(v...))
}
func (l WindowsLogger) Info(v ...interface{}) error {
return l.ev.Info(1, fmt.Sprint(v...))
}
func (l WindowsLogger) Errorf(format string, a ...interface{}) error {
return l.ev.Error(3, fmt.Sprintf(format, a...))
}
func (l WindowsLogger) Warningf(format string, a ...interface{}) error {
return l.ev.Warning(2, fmt.Sprintf(format, a...))
}
func (l WindowsLogger) Infof(format string, a ...interface{}) error {
return l.ev.Info(1, fmt.Sprintf(format, a...))
}
func (l WindowsLogger) NError(eventId uint32, v ...interface{}) error {
return l.ev.Error(eventId, fmt.Sprint(v...))
}
func (l WindowsLogger) NWarning(eventId uint32, v ...interface{}) error {
return l.ev.Warning(eventId, fmt.Sprint(v...))
}
func (l WindowsLogger) NInfo(eventId uint32, v ...interface{}) error {
return l.ev.Info(eventId, fmt.Sprint(v...))
}
func (l WindowsLogger) NErrorf(eventId uint32, format string, a ...interface{}) error {
return l.ev.Error(eventId, fmt.Sprintf(format, a...))
}
func (l WindowsLogger) NWarningf(eventId uint32, format string, a ...interface{}) error {
return l.ev.Warning(eventId, fmt.Sprintf(format, a...))
}
func (l WindowsLogger) NInfof(eventId uint32, format string, a ...interface{}) error {
return l.ev.Info(eventId, fmt.Sprintf(format, a...))
}
func isInteractive() (bool, error) {
return svc.IsAnInteractiveSession()
}
func newService(i Interface, c *Config) (*windowsService, error) {
ws := &windowsService{
i: i,
Config: c,
}
var err error
ws.interactive, err = isInteractive()
return ws, err
}
func (ws *windowsService) String() string {
if len(ws.DisplayName) > 0 {
return ws.DisplayName
}
return ws.Name
}
func (ws *windowsService) Execute(args []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (bool, uint32) {
const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown
changes <- svc.Status{State: svc.StartPending}
if err := ws.onStart(); err != nil {
ws.Error(err.Error())
if err := ws.i.Start(ws); err != nil {
// TODO: log error.
// ws.Error(err.Error())
return true, 1
}
@@ -47,10 +112,10 @@ loop:
changes <- c.CurrentStatus
case svc.Stop, svc.Shutdown:
changes <- svc.Status{State: svc.StopPending}
if err := ws.onStop(); err != nil {
ws.Error(err.Error())
changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted}
continue loop
if err := ws.i.Stop(ws); err != nil {
// TODO: Log error.
// ws.Error(err.Error())
return true, 2
}
break loop
default:
@@ -58,7 +123,11 @@ loop:
}
}
return
return false, 0
}
func (ws *windowsService) Interactive() bool {
return ws.interactive
}
func (ws *windowsService) Install() error {
@@ -73,21 +142,21 @@ func (ws *windowsService) Install() error {
return err
}
defer m.Disconnect()
s, err := m.OpenService(ws.name)
s, err := m.OpenService(ws.Name)
if err == nil {
s.Close()
return fmt.Errorf("service %s already exists", ws.name)
return fmt.Errorf("service %s already exists", ws.Name)
}
s, err = m.CreateService(ws.name, exepath, mgr.Config{
DisplayName: ws.displayName,
Description: ws.description,
s, err = m.CreateService(ws.Name, exepath, mgr.Config{
DisplayName: ws.DisplayName,
Description: ws.Description,
StartType: mgr.StartAutomatic,
})
if err != nil {
return err
}
defer s.Close()
err = eventlog.InstallAsEventCreate(ws.name, eventlog.Error|eventlog.Warning|eventlog.Info)
err = eventlog.InstallAsEventCreate(ws.Name, eventlog.Error|eventlog.Warning|eventlog.Info)
if err != nil {
s.Delete()
return fmt.Errorf("InstallAsEventCreate() failed: %s", err)
@@ -101,34 +170,38 @@ func (ws *windowsService) Remove() error {
return err
}
defer m.Disconnect()
s, err := m.OpenService(ws.name)
s, err := m.OpenService(ws.Name)
if err != nil {
return fmt.Errorf("service %s is not installed", ws.name)
return fmt.Errorf("service %s is not installed", ws.Name)
}
defer s.Close()
err = s.Delete()
if err != nil {
return err
}
err = eventlog.Remove(ws.name)
err = eventlog.Remove(ws.Name)
if err != nil {
return fmt.Errorf("RemoveEventLogSource() failed: %s", err)
}
return nil
}
func (ws *windowsService) Run(onStart, onStop func() error) error {
elog, err := eventlog.Open(ws.name)
func (ws *windowsService) Run() error {
if !ws.interactive {
return svc.Run(ws.Name, ws)
}
err := ws.i.Start(ws)
if err != nil {
return err
}
defer elog.Close()
ws.logger = elog
sigChan := make(chan os.Signal)
ws.onStart = onStart
ws.onStop = onStop
return svc.Run(ws.name, ws)
signal.Notify(sigChan, os.Interrupt, os.Kill)
<-sigChan
return ws.i.Stop(ws)
}
func (ws *windowsService) Start() error {
@@ -138,7 +211,7 @@ func (ws *windowsService) Start() error {
}
defer m.Disconnect()
s, err := m.OpenService(ws.name)
s, err := m.OpenService(ws.Name)
if err != nil {
return err
}
@@ -153,7 +226,7 @@ func (ws *windowsService) Stop() error {
}
defer m.Disconnect()
s, err := m.OpenService(ws.name)
s, err := m.OpenService(ws.Name)
if err != nil {
return err
}
@@ -162,21 +235,24 @@ func (ws *windowsService) Stop() error {
return err
}
func (ws *windowsService) Error(format string, a ...interface{}) error {
if ws.logger == nil {
return nil
func (ws *windowsService) Restart() error {
err := ws.Stop()
if err != nil {
return err
}
return ws.logger.Error(3, fmt.Sprintf(format, a...))
time.Sleep(50 * time.Millisecond)
return ws.Start()
}
func (ws *windowsService) Warning(format string, a ...interface{}) error {
if ws.logger == nil {
return nil
func (ws *windowsService) Logger() (Logger, error) {
if ws.interactive {
return ConsoleLogger, nil
}
return ws.logger.Warning(2, fmt.Sprintf(format, a...))
return ws.SystemLogger()
}
func (ws *windowsService) Info(format string, a ...interface{}) error {
if ws.logger == nil {
return nil
func (ws *windowsService) SystemLogger() (Logger, error) {
el, err := eventlog.Open(ws.Name)
if err != nil {
return nil, err
}
return ws.logger.Info(1, fmt.Sprintf(format, a...))
return WindowsLogger{el}, nil
}
-18
View File
@@ -1,18 +0,0 @@
package stdservice
import "fmt"
type ConsoleLogger struct{}
func (ConsoleLogger) Error(format string, a ...interface{}) error {
fmt.Printf("E: "+format+"\n", a...)
return nil
}
func (ConsoleLogger) Warning(format string, a ...interface{}) error {
fmt.Printf("W: "+format+"\n", a...)
return nil
}
func (ConsoleLogger) Info(format string, a ...interface{}) error {
fmt.Printf("I: "+format+"\n", a...)
return nil
}
-153
View File
@@ -1,153 +0,0 @@
/*
Many services that run on different platforms cannot rely
on flags to be passed for configuration. Some platforms
require explicit install commands. This package handles the common
boilerplate code. The following command may be passed to the
executable as the first argument:
install | remove | run | start | stop
These commands will do the following actions:
install - Install the running executable as a service on the system.
remove - Remove the running executable as a service on the system.
run - Run the service as a command line application, output log to prompt.
start - Starts the service via system controls.
stop - Stops the service via system controls.
*/
package stdservice
import (
"fmt"
"os"
"bitbucket.org/kardianos/service"
)
// Standard service configuration. Start MUST block.
// Stop MUST NOT block for more then a second or two.
type Config struct {
// Used to register the service with the operating system.
Name, DisplayName, LongDescription string
// Called when the service starts or stops.
// Stop may be nil.
Start, Stop func(c *Config)
// Called after logging may be setup but before the service is started.
// Init is optional and may be nil.
// If Init returns an error, that error is logged to the logger
// and the service start is aborted.
// Init should not block.
Init func(c *Config) error
s service.Service
l service.Logger
}
// Get service after Run() has been called.
func (c *Config) Service() service.Service {
return c.s
}
// Get logger after Run() has been called.
func (c *Config) Logger() service.Logger {
return c.l
}
// Fill in configuration, then call Run() to setup basic handling.
// Blocks until program completes. Is intended to handle the standard
// simple cases for running a service.
func (c *Config) Run() {
run(c)
}
// Depreciated. Same as *Config.Run().
func Run(c *Config) {
run(c)
}
func run(c *Config) {
var s, err = service.NewService(c.Name, c.DisplayName, c.LongDescription)
c.s = s
c.l = s
if err != nil {
fmt.Printf("%s unable to start: %s", c.DisplayName, err)
return
}
if len(os.Args) > 1 {
var err error
verb := os.Args[1]
switch verb {
case "install":
err = s.Install()
if err != nil {
fmt.Printf("Failed to install: %s\n", err)
return
}
fmt.Printf("Service \"%s\" installed.\n", c.DisplayName)
case "remove":
err = s.Remove()
if err != nil {
fmt.Printf("Failed to remove: %s\n", err)
return
}
fmt.Printf("Service \"%s\" removed.\n", c.DisplayName)
case "run":
c.l = ConsoleLogger{}
defer func() {
if c.Stop != nil {
c.Stop(c)
}
}()
if c.Init != nil {
err := c.Init(c)
if err != nil {
c.l.Error(err.Error())
return
}
}
c.Start(c)
case "start":
err = s.Start()
if err != nil {
fmt.Printf("Failed to start: %s\n", err)
return
}
fmt.Printf("Service \"%s\" started.\n", c.DisplayName)
case "stop":
err = s.Stop()
if err != nil {
fmt.Printf("Failed to stop: %s\n", err)
return
}
fmt.Printf("Service \"%s\" stopped.\n", c.DisplayName)
default:
fmt.Printf("Options for \"%s\": (install | remove | run | start | stop)\n", os.Args[0])
}
return
}
if c.Init != nil {
err := c.Init(c)
if err != nil {
c.l.Error(err.Error())
return
}
}
err = s.Run(func() error {
// start
go c.Start(c)
return nil
}, func() error {
// stop
if c.Stop != nil {
c.Stop(c)
}
return nil
})
if err != nil {
c.l.Error(err.Error())
}
}