Skip to content

Commit

Permalink
init
Browse files Browse the repository at this point in the history
  • Loading branch information
matthiasng committed Dec 5, 2019
1 parent 806ae62 commit 391dc1a
Show file tree
Hide file tree
Showing 11 changed files with 931 additions and 205 deletions.
81 changes: 81 additions & 0 deletions command/host.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package command

import (
"fmt"
"os"
"os/exec"
"path"
"time"

"github.com/matthiasng/service-shark/service"
)

type Config struct {
Name string
Arguments []string
}

type Host struct {
CmdConfig Config
LogDirecotry string
cmd *exec.Cmd
logFile *os.File
quitSignal chan struct{}
quitCompleted chan struct{}
}

func (h *Host) Init(env service.Environment) error {
h.cmd = exec.Command(h.CmdConfig.Name, h.CmdConfig.Arguments...)

if env.IsWindowsService() {
err := os.MkdirAll(h.LogDirecotry, os.ModePerm)
if err != nil {
return err
}

name := "" // #todo

logFileName := fmt.Sprintf("%s_%s.log", name, time.Now().Format("02-01-2006_15-04-05"))
logFilePath := path.Join(h.LogDirecotry, logFileName)

logFile, err := os.Create(logFilePath)
if err != nil {
return err
}

h.cmd.Stdout = logFile
h.cmd.Stderr = logFile
} else {
h.cmd.Stdout = os.Stdout
h.cmd.Stderr = os.Stderr
}

return h.cmd.Start()
}

func (h *Host) Start() error {
h.quitSignal = make(chan struct{})
h.quitCompleted = make(chan struct{})

go func() {
<-h.quitSignal
_ = h.cmd.Process.Kill() // #todo kill child process. Test Command -> "C:/Program Files/PowerShell/7-preview/preview/pwsh-preview.cmd"
close(h.quitCompleted)
}()

go func() {
_ = h.cmd.Wait()
os.Exit(1) // service command stopped -> stop service
}()

return nil
}

func (h *Host) Stop() error {
close(h.quitSignal)
<-h.quitCompleted

_ = h.logFile.Close()

return nil
}
7 changes: 2 additions & 5 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
module github.com/matthiasng/service-wrapper
module github.com/matthiasng/service-shark

go 1.13

require (
github.com/akamensky/argparse v0.0.0-20191006154803-1427fe674291
golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9
)
require golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e
6 changes: 2 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,4 +1,2 @@
github.com/akamensky/argparse v0.0.0-20191006154803-1427fe674291 h1:EQ9p1v9+urDzbGQfAcBf9fGuDtYqFNE7YJHZ54TWPTk=
github.com/akamensky/argparse v0.0.0-20191006154803-1427fe674291/go.mod h1:pdh+2piXurh466J9tqIqq39/9GO2Y8nZt6Cxzu18T9A=
golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9 h1:ZBzSG/7F4eNKz2L3GE9o300RX0Az1Bw5HF7PDraD+qU=
golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e h1:9vRrk9YW2BTzLP0VCB9ZDjU4cPqkg+IDWL7XgxA1yxQ=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
114 changes: 40 additions & 74 deletions main.go
Original file line number Diff line number Diff line change
@@ -1,86 +1,52 @@
package main

import (
"fmt"
"log"
"os"
"path"
"time"

"github.com/akamensky/argparse"
"github.com/matthiasng/service-wrapper/cli"
"github.com/matthiasng/service-wrapper/service"
"golang.org/x/sys/windows/svc"
"github.com/matthiasng/service-shark/command"
"github.com/matthiasng/service-shark/service"
)

func main() {
parser := argparse.NewParser("service-wrapper", `Run a "-command" with "-arguments" as service`)

serviceName := parser.String("n", "name", &argparse.Options{
Required: true,
Help: "Servicename",
})
logDirectory := parser.String("l", "logdirectory", &argparse.Options{
Required: true,
Help: "Log directory",
})
command := parser.String("c", "command", &argparse.Options{
Required: true,
Help: "Command",
})
arguments := parser.List("a", "arg", &argparse.Options{
Required: true,
Help: `Command arguments. Example: '... -a "-key" -a "value" -a "--key2" -a "value"'`,
})

err := parser.Parse(os.Args)
if err != nil {
fmt.Print(parser.Usage(err))
os.Exit(2)
}

//
isAnInteractiveSession, err := svc.IsAnInteractiveSession()
if err != nil {
log.Fatalf("failed to determine if we are running in an interactive session: %v", err)
}

//
logger := service.Logger{
Stdout: os.Stdout,
Stderr: os.Stderr,
}

if !isAnInteractiveSession {
err = os.MkdirAll(*logDirectory, os.ModePerm)
if err != nil {
log.Fatalf("Error - os.MkdirAll(%s, os.ModePerm): %v", *logDirectory, err)
}
// parser := argparse.NewParser("service-wrapper", `Run a "-command" with "-arguments" as service`)

// serviceName := parser.String("n", "name", &argparse.Options{
// Required: true,
// Help: "Servicename",
// })
// logDirectory := parser.String("l", "logdirectory", &argparse.Options{
// Required: true,
// Help: "Log directory",
// })
// command := parser.String("c", "command", &argparse.Options{
// Required: true,
// Help: "Command",
// })
// arguments := parser.List("a", "arg", &argparse.Options{
// Required: true,
// Help: `Command arguments. Example: '... -a "-key" -a "value" -a "--key2" -a "value"'`,
// })

// err := parser.Parse(os.Args)
// if err != nil {
// fmt.Print(parser.Usage(err))
// os.Exit(2)
// }

// cli.BindArguments(*arguments),

logFileName := fmt.Sprintf("%s_%s.log", *serviceName, time.Now().Format("02-01-2006_15-04-05"))
logFilePath := path.Join(*logDirectory, logFileName)
file, err := os.Create(logFilePath)
if err != nil {
log.Fatalf("Error - os.Create(%s): %v", logFilePath, err)
}
defer func() { _ = file.Close() }()

logger.Stdout = file
logger.Stderr = file
}

//
wrapper := service.Wrapper{
Config: service.Configuration{
IsAnInteractiveSession: isAnInteractiveSession,
ServiceName: *serviceName,
Command: *command,
Arguments: cli.BindArguments(*arguments),
Logger: logger,
func main() {
host := command.Host{
CmdConfig: command.Config{
Name: "powershell",
//Name: "C:/Program Files/PowerShell/7-preview/preview/pwsh-preview.cmd",
Arguments: []string{
`P:\_dev\projects\service-shark\example\test-service.ps1`,
},
},
LogDirecotry: "c:/tmp",
}
err = wrapper.Run()
if err != nil {
log.Fatalf("wrapper.Run(): %v", err)

if err := service.Run(&host); err != nil {
log.Fatal(err)
}
}
80 changes: 30 additions & 50 deletions service/service.go
Original file line number Diff line number Diff line change
@@ -1,55 +1,35 @@
package service

import (
"fmt"
"os"

"golang.org/x/sys/windows/svc"
)

type winService struct {
wrapper *Wrapper
import "os/signal"

// Create variable signal.Notify function so we can mock it in tests
var signalNotify = signal.Notify

// Service interface contains Start and Stop methods which are called
// when the service is started and stopped. The Init method is called
// before the service is started, and after it's determined if the program
// is running as a Windows Service.
//
// The Start and Init methods must be non-blocking.
//
// Implement this interface and pass it to the Run function to start your program.
type Service interface {
// Init is called before the program/service is started and after it's
// determined if the program is running as a Windows Service. This method must
// be non-blocking.
Init(Environment) error

// Start is called after Init. This method must be non-blocking.
Start() error

// Stop is called in response to syscall.SIGINT, syscall.SIGTERM, or when a
// Windows Service is stopped.
Stop() error
}

func (w *winService) Execute(args []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (svcSpecificEC bool, exitCode uint32) {
changes <- svc.Status{State: svc.StartPending}

err := w.wrapper.cmd.Start()
if err != nil {
_ = w.wrapper.eventLog.Error(1, fmt.Sprintf("Error - cmd.Start(): %v", err))
return true, 1
}

go func() {
err = w.wrapper.cmd.Wait()
if err != nil {
_ = w.wrapper.eventLog.Error(1, fmt.Sprintf("Error - cmd.Wait(): %v", err))
}
os.Exit(1) // error because our service command stopped
}()

changes <- svc.Status{State: svc.Running, Accepts: svc.AcceptStop | svc.AcceptShutdown}

loop:
for {
c := <-r
switch c.Cmd {
case svc.Interrogate:
changes <- c.CurrentStatus
case svc.Stop, svc.Shutdown:
changes <- svc.Status{State: svc.StopPending}

err := w.wrapper.cmd.Process.Kill() // #todo kill child process. Test Command -> "C:/Program Files/PowerShell/7-preview/preview/pwsh-preview.cmd"
if err != nil {
_ = w.wrapper.eventLog.Error(1, fmt.Sprintf("Error - cmd.Process.Kill(): %v", err))
}

changes <- svc.Status{State: svc.Stopped}
break loop
default:
continue loop
}
}

return false, 0
// Environment contains information about the environment
// your application is running in.
type Environment interface {
// IsWindowsService reports whether the program is running as a Windows Service.
IsWindowsService() bool
}
37 changes: 37 additions & 0 deletions service/service_other.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// +build !windows

package service

import (
"os"
"syscall"
)

// Run runs your Service.
//
// Run will block until one of the signals specified in sig is received.
// If sig is empty syscall.SIGINT and syscall.SIGTERM are used by default.
func Run(service Service) error {
env := environment{}
if err := service.Init(env); err != nil {
return err
}

if err := service.Start(); err != nil {
return err
}

sig := []os.Signal{syscall.SIGINT, syscall.SIGTERM}

signalChan := make(chan os.Signal, 1)
signalNotify(signalChan, sig...)
<-signalChan

return service.Stop()
}

type environment struct{}

func (environment) IsWindowsService() bool {
return false
}
Loading

0 comments on commit 391dc1a

Please sign in to comment.