Break Service interface into smaller chunks. Add Start and Stop methods.
This commit is contained in:
+28
-9
@@ -6,11 +6,15 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var localLog service.Logger
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
var name = "GoServiceTest"
|
var name = "GoServiceTest"
|
||||||
var displayName = "Go Service Test"
|
var displayName = "Go Service Test"
|
||||||
var desc = "This is a test Go service. It is designed to run well."
|
var desc = "This is a test Go service. It is designed to run well."
|
||||||
var ws, err = service.NewService(name, displayName, desc)
|
|
||||||
|
var s, err = service.NewService(name, displayName, desc)
|
||||||
|
localLog = s
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("%s unable to start: %s", displayName, err)
|
fmt.Printf("%s unable to start: %s", displayName, err)
|
||||||
@@ -22,41 +26,56 @@ func main() {
|
|||||||
verb := os.Args[1]
|
verb := os.Args[1]
|
||||||
switch verb {
|
switch verb {
|
||||||
case "install":
|
case "install":
|
||||||
err = ws.Install()
|
err = s.Install()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Failed to install: %s\n", err)
|
fmt.Printf("Failed to install: %s\n", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fmt.Printf("Service \"%s\" installed.\n", displayName)
|
fmt.Printf("Service \"%s\" installed.\n", displayName)
|
||||||
case "remove":
|
case "remove":
|
||||||
err = ws.Remove()
|
err = s.Remove()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Failed to remove: %s\n", err)
|
fmt.Printf("Failed to remove: %s\n", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fmt.Printf("Service \"%s\" removed.\n", displayName)
|
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)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = ws.Run(func() error {
|
err = s.Run(func() error {
|
||||||
// start
|
// start
|
||||||
go doWork()
|
go doWork()
|
||||||
ws.LogInfo("I'm Running!")
|
|
||||||
return nil
|
return nil
|
||||||
}, func() error {
|
}, func() error {
|
||||||
// stop
|
// stop
|
||||||
stopWork()
|
stopWork()
|
||||||
ws.LogInfo("I'm Stopping!")
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ws.LogError(err.Error())
|
s.LogError(err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func doWork() {
|
func doWork() {
|
||||||
|
localLog.LogInfo("I'm Running!")
|
||||||
|
select {}
|
||||||
}
|
}
|
||||||
func stopWork() {
|
func stopWork() {
|
||||||
|
localLog.LogInfo("I'm Stopping!")
|
||||||
}
|
}
|
||||||
|
|||||||
+32
-8
@@ -1,5 +1,5 @@
|
|||||||
// Package service provides a simple way to create a system service.
|
// Package service provides a simple way to create a system service.
|
||||||
// Currently supports Windows and Linux/Upstart.
|
// Currently supports Windows, Linux/Upstart, OSX/Launchd.
|
||||||
package service
|
package service
|
||||||
|
|
||||||
// Creates a new service. name is the internal name
|
// Creates a new service. name is the internal name
|
||||||
@@ -12,14 +12,14 @@ func NewService(name, displayName, description string) (Service, error) {
|
|||||||
|
|
||||||
// Represents a generic way to interact with the system's service.
|
// Represents a generic way to interact with the system's service.
|
||||||
type Service interface {
|
type Service interface {
|
||||||
// Installs this service on the system. May return an
|
Installer
|
||||||
// error if this service is already installed.
|
Controller
|
||||||
Install() error
|
Runner
|
||||||
|
Logger
|
||||||
// Removes this service from the system. May return an
|
}
|
||||||
// error if this service is not already installed.
|
|
||||||
Remove() error
|
|
||||||
|
|
||||||
|
// A Generic way to stop and start a service.
|
||||||
|
type Runner interface {
|
||||||
// Call quickly after initial entry point. Does not return until
|
// Call quickly after initial entry point. Does not return until
|
||||||
// service is ready to stop. onStart is called when the service is
|
// service is ready to stop. onStart is called when the service is
|
||||||
// starting, returning an error will fail to start the service.
|
// starting, returning an error will fail to start the service.
|
||||||
@@ -28,7 +28,31 @@ type Service interface {
|
|||||||
// an error from Run.
|
// an error from Run.
|
||||||
// Both callbacks should return quickly and not block.
|
// Both callbacks should return quickly and not block.
|
||||||
Run(onStart, onStop func() error) error
|
Run(onStart, onStop func() error) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
|
||||||
|
// Removes this service from the system. May return an
|
||||||
|
// error if this service is not already installed.
|
||||||
|
Remove() error
|
||||||
|
}
|
||||||
|
|
||||||
|
// A service that implements ServiceController is able to
|
||||||
|
// start and stop itself.
|
||||||
|
type Controller interface {
|
||||||
|
// Starts this service on the system.
|
||||||
|
Start() error
|
||||||
|
|
||||||
|
// Stops this service on the system.
|
||||||
|
Stop() error
|
||||||
|
}
|
||||||
|
|
||||||
|
// A service that implements ServiceLogger can perform simple system logging.
|
||||||
|
type Logger interface {
|
||||||
// Basic log functions in the context of the service.
|
// Basic log functions in the context of the service.
|
||||||
LogError(format string, a ...interface{}) error
|
LogError(format string, a ...interface{}) error
|
||||||
LogWarning(format string, a ...interface{}) error
|
LogWarning(format string, a ...interface{}) error
|
||||||
|
|||||||
+22
-25
@@ -5,8 +5,8 @@ package service
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
int
|
int
|
||||||
GetExecPath(char* path) {
|
GetExecPath(char* path, int bufferSize) {
|
||||||
uint32_t size = 32*1024;
|
uint32_t size = bufferSize;
|
||||||
if (_NSGetExecutablePath(path, &size) == 0) {
|
if (_NSGetExecutablePath(path, &size) == 0) {
|
||||||
// Despite Apple docs, size does NOT get set in call.
|
// Despite Apple docs, size does NOT get set in call.
|
||||||
return strlen(path);
|
return strlen(path);
|
||||||
@@ -28,7 +28,8 @@ import (
|
|||||||
"text/template"
|
"text/template"
|
||||||
)
|
)
|
||||||
|
|
||||||
// BUG(kardia): I have not confirmed this service works as intended on launchd.
|
const maxPathSize = 32 * 1024
|
||||||
|
|
||||||
func newService(name, displayName, description string) (s *darwinLaunchdService, err error) {
|
func newService(name, displayName, description string) (s *darwinLaunchdService, err error) {
|
||||||
s = &darwinLaunchdService{
|
s = &darwinLaunchdService{
|
||||||
name: name,
|
name: name,
|
||||||
@@ -83,30 +84,26 @@ func (s *darwinLaunchdService) Install() error {
|
|||||||
path,
|
path,
|
||||||
}
|
}
|
||||||
|
|
||||||
t := template.Must(template.New("upstartScript").Parse(upstartScript))
|
t := template.Must(template.New("launchdConfig").Parse(launchdConfig))
|
||||||
err = t.Execute(f, to)
|
return t.Execute(f, to)
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := exec.Command("launchctl", "load", confPath)
|
|
||||||
err = cmd.Run()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *darwinLaunchdService) Remove() error {
|
func (s *darwinLaunchdService) Remove() error {
|
||||||
|
s.Stop()
|
||||||
|
|
||||||
|
confPath := s.getServiceFilePath()
|
||||||
|
return os.Remove(confPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *darwinLaunchdService) Start() error {
|
||||||
|
confPath := s.getServiceFilePath()
|
||||||
|
cmd := exec.Command("launchctl", "load", confPath)
|
||||||
|
return cmd.Run()
|
||||||
|
}
|
||||||
|
func (s *darwinLaunchdService) Stop() error {
|
||||||
confPath := s.getServiceFilePath()
|
confPath := s.getServiceFilePath()
|
||||||
cmd := exec.Command("launchctl", "unload", confPath)
|
cmd := exec.Command("launchctl", "unload", confPath)
|
||||||
err := cmd.Run()
|
return cmd.Run()
|
||||||
|
|
||||||
// Don't worry about his error if not found.
|
|
||||||
os.Remove(confPath)
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *darwinLaunchdService) Run(onStart, onStop func() error) error {
|
func (s *darwinLaunchdService) Run(onStart, onStop func() error) error {
|
||||||
@@ -137,8 +134,8 @@ func (s *darwinLaunchdService) LogInfo(format string, a ...interface{}) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getExePath() (exePath string, err error) {
|
func getExePath() (exePath string, err error) {
|
||||||
buffer := make([]byte, 32*1024)
|
buffer := make([]byte, maxPathSize)
|
||||||
size := C.GetExecPath((*C.char)(unsafe.Pointer(&buffer[0])))
|
size := C.GetExecPath((*C.char)(unsafe.Pointer(&buffer[0])), maxPathSize)
|
||||||
if size == 0 {
|
if size == 0 {
|
||||||
return "", errors.New("Unable to get exec path.")
|
return "", errors.New("Unable to get exec path.")
|
||||||
}
|
}
|
||||||
@@ -147,7 +144,7 @@ func getExePath() (exePath string, err error) {
|
|||||||
return ret, nil
|
return ret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var upstartScript = `<?xml version='1.0' encoding='UTF-8'?>
|
var launchdConfig = `<?xml version='1.0' encoding='UTF-8'?>
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN"
|
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN"
|
||||||
"http://www.apple.com/DTDs/PropertyList-1.0.dtd" >
|
"http://www.apple.com/DTDs/PropertyList-1.0.dtd" >
|
||||||
<plist version='1.0'>
|
<plist version='1.0'>
|
||||||
|
|||||||
+15
-1
@@ -4,8 +4,10 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log/syslog"
|
"log/syslog"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
"path/filepath"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newService(name, displayName, description string) (s *linuxUpstartService, err error) {
|
func newService(name, displayName, description string) (s *linuxUpstartService, err error) {
|
||||||
@@ -87,6 +89,16 @@ func (s *linuxUpstartService) Run(onStart, onStop func() error) error {
|
|||||||
return onStop()
|
return onStop()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *linuxUpstartService) Start() error {
|
||||||
|
cmd := exec.Command("start", s.name)
|
||||||
|
return cmd.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *linuxUpstartService) Stop() error {
|
||||||
|
cmd := exec.Command("stop", s.name)
|
||||||
|
return cmd.Run()
|
||||||
|
}
|
||||||
|
|
||||||
func (s *linuxUpstartService) LogError(format string, a ...interface{}) error {
|
func (s *linuxUpstartService) LogError(format string, a ...interface{}) error {
|
||||||
return s.logger.Err(fmt.Sprintf(format, a...))
|
return s.logger.Err(fmt.Sprintf(format, a...))
|
||||||
}
|
}
|
||||||
@@ -98,7 +110,9 @@ func (s *linuxUpstartService) LogInfo(format string, a ...interface{}) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getExePath() (exePath string, err error) {
|
func getExePath() (exePath string, err error) {
|
||||||
return os.Readlink(`/proc/self/exe`)
|
exePath, err = os.Readlink(`/proc/self/exe`)
|
||||||
|
exePath = filepath.Clean(exePath)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var upstartScript = `# {{.Description}}
|
var upstartScript = `# {{.Description}}
|
||||||
|
|||||||
@@ -124,6 +124,37 @@ func (ws *windowsService) Run(onStart, onStop func() error) error {
|
|||||||
return svc.Run(ws.name, ws)
|
return svc.Run(ws.name, ws)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ws *windowsService) Start() error {
|
||||||
|
m, err := mgr.Connect()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer m.Disconnect()
|
||||||
|
|
||||||
|
s, err := m.OpenService(ws.name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer s.Close()
|
||||||
|
return s.Start([]string{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ws *windowsService) Stop() error {
|
||||||
|
m, err := mgr.Connect()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer m.Disconnect()
|
||||||
|
|
||||||
|
s, err := m.OpenService(ws.name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer s.Close()
|
||||||
|
_, err = s.Control(svc.Stop)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
func (ws *windowsService) LogError(format string, a ...interface{}) error {
|
func (ws *windowsService) LogError(format string, a ...interface{}) error {
|
||||||
if ws.logger == nil {
|
if ws.logger == nil {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
Reference in New Issue
Block a user