Merge: Dsheets service options
Merge in DNS/adguard-home from dsheets-service-options to master * commit '7be0cc72df9b01922e6bc5b3e6e9c6ab57b3fc26': Don't run home_test with race detection service: installation and running of AGH as a service with CLI args home/options: add options -> args serialization cli: factor options struct and parsing into home/options.go
This commit is contained in:
commit
3c85b994d3
|
@ -1,6 +1,10 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
set -e;
|
set -e;
|
||||||
git diff --cached --name-only | grep -q '.js$' && make lint-js;
|
git diff --cached --name-only | grep -q '.js$' && found=1
|
||||||
|
if [ $found == 1 ]; then
|
||||||
|
make lint-js || exit 1
|
||||||
|
npm run test --prefix client || exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
found=0
|
found=0
|
||||||
git diff --cached --name-only | grep -q '.go$' && found=1
|
git diff --cached --name-only | grep -q '.go$' && found=1
|
||||||
|
|
109
home/home.go
109
home/home.go
|
@ -126,7 +126,7 @@ func Main(version string, channel string, armVer string) {
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if args.serviceControlAction != "" {
|
if args.serviceControlAction != "" {
|
||||||
handleServiceControlAction(args.serviceControlAction)
|
handleServiceControlAction(args)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -526,108 +526,25 @@ func cleanupAlways() {
|
||||||
log.Info("Stopped")
|
log.Info("Stopped")
|
||||||
}
|
}
|
||||||
|
|
||||||
// command-line arguments
|
func exitWithError() {
|
||||||
type options struct {
|
os.Exit(64)
|
||||||
verbose bool // is verbose logging enabled
|
|
||||||
configFilename string // path to the config file
|
|
||||||
workDir string // path to the working directory where we will store the filters data and the querylog
|
|
||||||
bindHost string // host address to bind HTTP server on
|
|
||||||
bindPort int // port to serve HTTP pages on
|
|
||||||
logFile string // Path to the log file. If empty, write to stdout. If "syslog", writes to syslog
|
|
||||||
pidFile string // File name to save PID to
|
|
||||||
checkConfig bool // Check configuration and exit
|
|
||||||
disableUpdate bool // If set, don't check for updates
|
|
||||||
|
|
||||||
// service control action (see service.ControlAction array + "status" command)
|
|
||||||
serviceControlAction string
|
|
||||||
|
|
||||||
// runningAsService flag is set to true when options are passed from the service runner
|
|
||||||
runningAsService bool
|
|
||||||
|
|
||||||
glinetMode bool // Activate GL-Inet mode
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// loadOptions reads command line arguments and initializes configuration
|
// loadOptions reads command line arguments and initializes configuration
|
||||||
func loadOptions() options {
|
func loadOptions() options {
|
||||||
o := options{}
|
o, f, err := parse(os.Args[0], os.Args[1:])
|
||||||
|
|
||||||
var printHelp func()
|
|
||||||
var opts = []struct {
|
|
||||||
longName string
|
|
||||||
shortName string
|
|
||||||
description string
|
|
||||||
callbackWithValue func(value string)
|
|
||||||
callbackNoValue func()
|
|
||||||
}{
|
|
||||||
{"config", "c", "Path to the config file", func(value string) { o.configFilename = value }, nil},
|
|
||||||
{"work-dir", "w", "Path to the working directory", func(value string) { o.workDir = value }, nil},
|
|
||||||
{"host", "h", "Host address to bind HTTP server on", func(value string) { o.bindHost = value }, nil},
|
|
||||||
{"port", "p", "Port to serve HTTP pages on", func(value string) {
|
|
||||||
v, err := strconv.Atoi(value)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic("Got port that is not a number")
|
log.Error(err.Error())
|
||||||
}
|
_ = printHelp(os.Args[0])
|
||||||
o.bindPort = v
|
exitWithError()
|
||||||
}, nil},
|
} else if f != nil {
|
||||||
{"service", "s", "Service control action: status, install, uninstall, start, stop, restart, reload (configuration)", func(value string) {
|
err = f()
|
||||||
o.serviceControlAction = value
|
if err != nil {
|
||||||
}, nil},
|
log.Error(err.Error())
|
||||||
{"logfile", "l", "Path to log file. If empty: write to stdout; if 'syslog': write to system log", func(value string) {
|
exitWithError()
|
||||||
o.logFile = value
|
|
||||||
}, nil},
|
|
||||||
{"pidfile", "", "Path to a file where PID is stored", func(value string) { o.pidFile = value }, nil},
|
|
||||||
{"check-config", "", "Check configuration and exit", nil, func() { o.checkConfig = true }},
|
|
||||||
{"no-check-update", "", "Don't check for updates", nil, func() { o.disableUpdate = true }},
|
|
||||||
{"verbose", "v", "Enable verbose output", nil, func() { o.verbose = true }},
|
|
||||||
{"glinet", "", "Run in GL-Inet compatibility mode", nil, func() { o.glinetMode = true }},
|
|
||||||
{"version", "", "Show the version and exit", nil, func() {
|
|
||||||
fmt.Println(version())
|
|
||||||
os.Exit(0)
|
|
||||||
}},
|
|
||||||
{"help", "", "Print this help", nil, func() {
|
|
||||||
printHelp()
|
|
||||||
os.Exit(64)
|
|
||||||
}},
|
|
||||||
}
|
|
||||||
printHelp = func() {
|
|
||||||
fmt.Printf("Usage:\n\n")
|
|
||||||
fmt.Printf("%s [options]\n\n", os.Args[0])
|
|
||||||
fmt.Printf("Options:\n")
|
|
||||||
for _, opt := range opts {
|
|
||||||
val := ""
|
|
||||||
if opt.callbackWithValue != nil {
|
|
||||||
val = " VALUE"
|
|
||||||
}
|
|
||||||
if opt.shortName != "" {
|
|
||||||
fmt.Printf(" -%s, %-30s %s\n", opt.shortName, "--"+opt.longName+val, opt.description)
|
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf(" %-34s %s\n", "--"+opt.longName+val, opt.description)
|
os.Exit(0)
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for i := 1; i < len(os.Args); i++ {
|
|
||||||
v := os.Args[i]
|
|
||||||
knownParam := false
|
|
||||||
for _, opt := range opts {
|
|
||||||
if v == "--"+opt.longName || (opt.shortName != "" && v == "-"+opt.shortName) {
|
|
||||||
if opt.callbackWithValue != nil {
|
|
||||||
if i+1 >= len(os.Args) {
|
|
||||||
log.Error("Got %s without argument\n", v)
|
|
||||||
os.Exit(64)
|
|
||||||
}
|
|
||||||
i++
|
|
||||||
opt.callbackWithValue(os.Args[i])
|
|
||||||
} else if opt.callbackNoValue != nil {
|
|
||||||
opt.callbackNoValue()
|
|
||||||
}
|
|
||||||
knownParam = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !knownParam {
|
|
||||||
log.Error("unknown option %v\n", v)
|
|
||||||
printHelp()
|
|
||||||
os.Exit(64)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
// +build !race
|
||||||
|
|
||||||
package home
|
package home
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
|
@ -0,0 +1,313 @@
|
||||||
|
package home
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// options passed from command-line arguments
|
||||||
|
type options struct {
|
||||||
|
verbose bool // is verbose logging enabled
|
||||||
|
configFilename string // path to the config file
|
||||||
|
workDir string // path to the working directory where we will store the filters data and the querylog
|
||||||
|
bindHost string // host address to bind HTTP server on
|
||||||
|
bindPort int // port to serve HTTP pages on
|
||||||
|
logFile string // Path to the log file. If empty, write to stdout. If "syslog", writes to syslog
|
||||||
|
pidFile string // File name to save PID to
|
||||||
|
checkConfig bool // Check configuration and exit
|
||||||
|
disableUpdate bool // If set, don't check for updates
|
||||||
|
|
||||||
|
// service control action (see service.ControlAction array + "status" command)
|
||||||
|
serviceControlAction string
|
||||||
|
|
||||||
|
// runningAsService flag is set to true when options are passed from the service runner
|
||||||
|
runningAsService bool
|
||||||
|
|
||||||
|
glinetMode bool // Activate GL-Inet mode
|
||||||
|
}
|
||||||
|
|
||||||
|
// functions used for their side-effects
|
||||||
|
type effect func() error
|
||||||
|
|
||||||
|
type arg struct {
|
||||||
|
description string // a short, English description of the argument
|
||||||
|
longName string // the name of the argument used after '--'
|
||||||
|
shortName string // the name of the argument used after '-'
|
||||||
|
|
||||||
|
// only one of updateWithValue, updateNoValue, and effect should be present
|
||||||
|
|
||||||
|
updateWithValue func(o options, v string) (options, error) // the mutator for arguments with parameters
|
||||||
|
updateNoValue func(o options) (options, error) // the mutator for arguments without parameters
|
||||||
|
effect func(o options, exec string) (f effect, err error) // the side-effect closure generator
|
||||||
|
|
||||||
|
serialize func(o options) []string // the re-serialization function back to arguments (return nil for omit)
|
||||||
|
}
|
||||||
|
|
||||||
|
// {type}SliceOrNil functions check their parameter of type {type}
|
||||||
|
// against its zero value and return nil if the parameter value is
|
||||||
|
// zero otherwise they return a string slice of the parameter
|
||||||
|
|
||||||
|
func stringSliceOrNil(s string) []string {
|
||||||
|
if s == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return []string{s}
|
||||||
|
}
|
||||||
|
|
||||||
|
func intSliceOrNil(i int) []string {
|
||||||
|
if i == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return []string{strconv.Itoa(i)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func boolSliceOrNil(b bool) []string {
|
||||||
|
if b {
|
||||||
|
return []string{}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var args []arg
|
||||||
|
|
||||||
|
var configArg = arg{
|
||||||
|
"Path to the config file",
|
||||||
|
"config", "c",
|
||||||
|
func(o options, v string) (options, error) { o.configFilename = v; return o, nil },
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
func(o options) []string { return stringSliceOrNil(o.configFilename) },
|
||||||
|
}
|
||||||
|
|
||||||
|
var workDirArg = arg{
|
||||||
|
"Path to the working directory",
|
||||||
|
"work-dir", "w",
|
||||||
|
func(o options, v string) (options, error) { o.workDir = v; return o, nil }, nil, nil,
|
||||||
|
func(o options) []string { return stringSliceOrNil(o.workDir) },
|
||||||
|
}
|
||||||
|
|
||||||
|
var hostArg = arg{
|
||||||
|
"Host address to bind HTTP server on",
|
||||||
|
"host", "h",
|
||||||
|
func(o options, v string) (options, error) { o.bindHost = v; return o, nil }, nil, nil,
|
||||||
|
func(o options) []string { return stringSliceOrNil(o.bindHost) },
|
||||||
|
}
|
||||||
|
|
||||||
|
var portArg = arg{
|
||||||
|
"Port to serve HTTP pages on",
|
||||||
|
"port", "p",
|
||||||
|
func(o options, v string) (options, error) {
|
||||||
|
var err error
|
||||||
|
var p int
|
||||||
|
minPort, maxPort := 0, 1<<16-1
|
||||||
|
if p, err = strconv.Atoi(v); err != nil {
|
||||||
|
err = fmt.Errorf("port '%s' is not a number", v)
|
||||||
|
} else if p < minPort || p > maxPort {
|
||||||
|
err = fmt.Errorf("port %d not in range %d - %d", p, minPort, maxPort)
|
||||||
|
} else {
|
||||||
|
o.bindPort = p
|
||||||
|
}
|
||||||
|
return o, err
|
||||||
|
}, nil, nil,
|
||||||
|
func(o options) []string { return intSliceOrNil(o.bindPort) },
|
||||||
|
}
|
||||||
|
|
||||||
|
var serviceArg = arg{
|
||||||
|
"Service control action: status, install, uninstall, start, stop, restart, reload (configuration)",
|
||||||
|
"service", "s",
|
||||||
|
func(o options, v string) (options, error) {
|
||||||
|
o.serviceControlAction = v
|
||||||
|
return o, nil
|
||||||
|
}, nil, nil,
|
||||||
|
func(o options) []string { return stringSliceOrNil(o.serviceControlAction) },
|
||||||
|
}
|
||||||
|
|
||||||
|
var logfileArg = arg{
|
||||||
|
"Path to log file. If empty: write to stdout; if 'syslog': write to system log",
|
||||||
|
"logfile", "l",
|
||||||
|
func(o options, v string) (options, error) { o.logFile = v; return o, nil }, nil, nil,
|
||||||
|
func(o options) []string { return stringSliceOrNil(o.logFile) },
|
||||||
|
}
|
||||||
|
|
||||||
|
var pidfileArg = arg{
|
||||||
|
"Path to a file where PID is stored",
|
||||||
|
"pidfile", "",
|
||||||
|
func(o options, v string) (options, error) { o.pidFile = v; return o, nil }, nil, nil,
|
||||||
|
func(o options) []string { return stringSliceOrNil(o.pidFile) },
|
||||||
|
}
|
||||||
|
|
||||||
|
var checkConfigArg = arg{
|
||||||
|
"Check configuration and exit",
|
||||||
|
"check-config", "",
|
||||||
|
nil, func(o options) (options, error) { o.checkConfig = true; return o, nil }, nil,
|
||||||
|
func(o options) []string { return boolSliceOrNil(o.checkConfig) },
|
||||||
|
}
|
||||||
|
|
||||||
|
var noCheckUpdateArg = arg{
|
||||||
|
"Don't check for updates",
|
||||||
|
"no-check-update", "",
|
||||||
|
nil, func(o options) (options, error) { o.disableUpdate = true; return o, nil }, nil,
|
||||||
|
func(o options) []string { return boolSliceOrNil(o.disableUpdate) },
|
||||||
|
}
|
||||||
|
|
||||||
|
var verboseArg = arg{
|
||||||
|
"Enable verbose output",
|
||||||
|
"verbose", "v",
|
||||||
|
nil, func(o options) (options, error) { o.verbose = true; return o, nil }, nil,
|
||||||
|
func(o options) []string { return boolSliceOrNil(o.verbose) },
|
||||||
|
}
|
||||||
|
|
||||||
|
var glinetArg = arg{
|
||||||
|
"Run in GL-Inet compatibility mode",
|
||||||
|
"glinet", "",
|
||||||
|
nil, func(o options) (options, error) { o.glinetMode = true; return o, nil }, nil,
|
||||||
|
func(o options) []string { return boolSliceOrNil(o.glinetMode) },
|
||||||
|
}
|
||||||
|
|
||||||
|
var versionArg = arg{
|
||||||
|
"Show the version and exit",
|
||||||
|
"version", "",
|
||||||
|
nil, nil, func(o options, exec string) (effect, error) {
|
||||||
|
return func() error { fmt.Println(version()); os.Exit(0); return nil }, nil
|
||||||
|
},
|
||||||
|
func(o options) []string { return nil },
|
||||||
|
}
|
||||||
|
|
||||||
|
var helpArg = arg{
|
||||||
|
"Print this help",
|
||||||
|
"help", "",
|
||||||
|
nil, nil, func(o options, exec string) (effect, error) {
|
||||||
|
return func() error { _ = printHelp(exec); os.Exit(64); return nil }, nil
|
||||||
|
},
|
||||||
|
func(o options) []string { return nil },
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
args = []arg{
|
||||||
|
configArg,
|
||||||
|
workDirArg,
|
||||||
|
hostArg,
|
||||||
|
portArg,
|
||||||
|
serviceArg,
|
||||||
|
logfileArg,
|
||||||
|
pidfileArg,
|
||||||
|
checkConfigArg,
|
||||||
|
noCheckUpdateArg,
|
||||||
|
verboseArg,
|
||||||
|
glinetArg,
|
||||||
|
versionArg,
|
||||||
|
helpArg,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getUsageLines(exec string, args []arg) []string {
|
||||||
|
usage := []string{
|
||||||
|
"Usage:",
|
||||||
|
"",
|
||||||
|
fmt.Sprintf("%s [options]", exec),
|
||||||
|
"",
|
||||||
|
"Options:",
|
||||||
|
}
|
||||||
|
for _, arg := range args {
|
||||||
|
val := ""
|
||||||
|
if arg.updateWithValue != nil {
|
||||||
|
val = " VALUE"
|
||||||
|
}
|
||||||
|
if arg.shortName != "" {
|
||||||
|
usage = append(usage, fmt.Sprintf(" -%s, %-30s %s",
|
||||||
|
arg.shortName,
|
||||||
|
"--"+arg.longName+val,
|
||||||
|
arg.description))
|
||||||
|
} else {
|
||||||
|
usage = append(usage, fmt.Sprintf(" %-34s %s",
|
||||||
|
"--"+arg.longName+val,
|
||||||
|
arg.description))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return usage
|
||||||
|
}
|
||||||
|
|
||||||
|
func printHelp(exec string) error {
|
||||||
|
for _, line := range getUsageLines(exec, args) {
|
||||||
|
_, err := fmt.Println(line)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func argMatches(a arg, v string) bool {
|
||||||
|
return v == "--"+a.longName || (a.shortName != "" && v == "-"+a.shortName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parse(exec string, ss []string) (o options, f effect, err error) {
|
||||||
|
for i := 0; i < len(ss); i++ {
|
||||||
|
v := ss[i]
|
||||||
|
knownParam := false
|
||||||
|
for _, arg := range args {
|
||||||
|
if argMatches(arg, v) {
|
||||||
|
if arg.updateWithValue != nil {
|
||||||
|
if i+1 >= len(ss) {
|
||||||
|
return o, f, fmt.Errorf("got %s without argument", v)
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
o, err = arg.updateWithValue(o, ss[i])
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else if arg.updateNoValue != nil {
|
||||||
|
o, err = arg.updateNoValue(o)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else if arg.effect != nil {
|
||||||
|
var eff effect
|
||||||
|
eff, err = arg.effect(o, exec)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if eff != nil {
|
||||||
|
prevf := f
|
||||||
|
f = func() error {
|
||||||
|
var err error
|
||||||
|
if prevf != nil {
|
||||||
|
err = prevf()
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
err = eff()
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
knownParam = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !knownParam {
|
||||||
|
return o, f, fmt.Errorf("unknown option %v", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func shortestFlag(a arg) string {
|
||||||
|
if a.shortName != "" {
|
||||||
|
return "-" + a.shortName
|
||||||
|
}
|
||||||
|
return "--" + a.longName
|
||||||
|
}
|
||||||
|
|
||||||
|
func serialize(o options) []string {
|
||||||
|
ss := []string{}
|
||||||
|
for _, arg := range args {
|
||||||
|
s := arg.serialize(o)
|
||||||
|
if s != nil {
|
||||||
|
ss = append(ss, append([]string{shortestFlag(arg)}, s...)...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ss
|
||||||
|
}
|
|
@ -0,0 +1,237 @@
|
||||||
|
package home
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func testParseOk(t *testing.T, ss ...string) options {
|
||||||
|
o, _, err := parse("", ss)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err.Error())
|
||||||
|
}
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
|
func testParseErr(t *testing.T, descr string, ss ...string) {
|
||||||
|
_, _, err := parse("", ss)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("expected an error because %s but no error returned", descr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testParseParamMissing(t *testing.T, param string) {
|
||||||
|
testParseErr(t, fmt.Sprintf("%s parameter missing", param), param)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseVerbose(t *testing.T) {
|
||||||
|
if testParseOk(t).verbose {
|
||||||
|
t.Fatal("empty is not verbose")
|
||||||
|
}
|
||||||
|
if !testParseOk(t, "-v").verbose {
|
||||||
|
t.Fatal("-v is verbose")
|
||||||
|
}
|
||||||
|
if !testParseOk(t, "--verbose").verbose {
|
||||||
|
t.Fatal("--verbose is verbose")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseConfigFilename(t *testing.T) {
|
||||||
|
if testParseOk(t).configFilename != "" {
|
||||||
|
t.Fatal("empty is no config filename")
|
||||||
|
}
|
||||||
|
if testParseOk(t, "-c", "path").configFilename != "path" {
|
||||||
|
t.Fatal("-c is config filename")
|
||||||
|
}
|
||||||
|
testParseParamMissing(t, "-c")
|
||||||
|
if testParseOk(t, "--config", "path").configFilename != "path" {
|
||||||
|
t.Fatal("--configFilename is config filename")
|
||||||
|
}
|
||||||
|
testParseParamMissing(t, "--config")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseWorkDir(t *testing.T) {
|
||||||
|
if testParseOk(t).workDir != "" {
|
||||||
|
t.Fatal("empty is no work dir")
|
||||||
|
}
|
||||||
|
if testParseOk(t, "-w", "path").workDir != "path" {
|
||||||
|
t.Fatal("-w is work dir")
|
||||||
|
}
|
||||||
|
testParseParamMissing(t, "-w")
|
||||||
|
if testParseOk(t, "--work-dir", "path").workDir != "path" {
|
||||||
|
t.Fatal("--work-dir is work dir")
|
||||||
|
}
|
||||||
|
testParseParamMissing(t, "--work-dir")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseBindHost(t *testing.T) {
|
||||||
|
if testParseOk(t).bindHost != "" {
|
||||||
|
t.Fatal("empty is no host")
|
||||||
|
}
|
||||||
|
if testParseOk(t, "-h", "addr").bindHost != "addr" {
|
||||||
|
t.Fatal("-h is host")
|
||||||
|
}
|
||||||
|
testParseParamMissing(t, "-h")
|
||||||
|
if testParseOk(t, "--host", "addr").bindHost != "addr" {
|
||||||
|
t.Fatal("--host is host")
|
||||||
|
}
|
||||||
|
testParseParamMissing(t, "--host")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseBindPort(t *testing.T) {
|
||||||
|
if testParseOk(t).bindPort != 0 {
|
||||||
|
t.Fatal("empty is port 0")
|
||||||
|
}
|
||||||
|
if testParseOk(t, "-p", "65535").bindPort != 65535 {
|
||||||
|
t.Fatal("-p is port")
|
||||||
|
}
|
||||||
|
testParseParamMissing(t, "-p")
|
||||||
|
if testParseOk(t, "--port", "65535").bindPort != 65535 {
|
||||||
|
t.Fatal("--port is port")
|
||||||
|
}
|
||||||
|
testParseParamMissing(t, "--port")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseBindPortBad(t *testing.T) {
|
||||||
|
testParseErr(t, "not an int", "-p", "x")
|
||||||
|
testParseErr(t, "hex not supported", "-p", "0x100")
|
||||||
|
testParseErr(t, "port negative", "-p", "-1")
|
||||||
|
testParseErr(t, "port too high", "-p", "65536")
|
||||||
|
testParseErr(t, "port too high", "-p", "4294967297") // 2^32 + 1
|
||||||
|
testParseErr(t, "port too high", "-p", "18446744073709551617") // 2^64 + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseLogfile(t *testing.T) {
|
||||||
|
if testParseOk(t).logFile != "" {
|
||||||
|
t.Fatal("empty is no log file")
|
||||||
|
}
|
||||||
|
if testParseOk(t, "-l", "path").logFile != "path" {
|
||||||
|
t.Fatal("-l is log file")
|
||||||
|
}
|
||||||
|
if testParseOk(t, "--logfile", "path").logFile != "path" {
|
||||||
|
t.Fatal("--logfile is log file")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParsePidfile(t *testing.T) {
|
||||||
|
if testParseOk(t).pidFile != "" {
|
||||||
|
t.Fatal("empty is no pid file")
|
||||||
|
}
|
||||||
|
if testParseOk(t, "--pidfile", "path").pidFile != "path" {
|
||||||
|
t.Fatal("--pidfile is pid file")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseCheckConfig(t *testing.T) {
|
||||||
|
if testParseOk(t).checkConfig {
|
||||||
|
t.Fatal("empty is not check config")
|
||||||
|
}
|
||||||
|
if !testParseOk(t, "--check-config").checkConfig {
|
||||||
|
t.Fatal("--check-config is check config")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseDisableUpdate(t *testing.T) {
|
||||||
|
if testParseOk(t).disableUpdate {
|
||||||
|
t.Fatal("empty is not disable update")
|
||||||
|
}
|
||||||
|
if !testParseOk(t, "--no-check-update").disableUpdate {
|
||||||
|
t.Fatal("--no-check-update is disable update")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseService(t *testing.T) {
|
||||||
|
if testParseOk(t).serviceControlAction != "" {
|
||||||
|
t.Fatal("empty is no service command")
|
||||||
|
}
|
||||||
|
if testParseOk(t, "-s", "command").serviceControlAction != "command" {
|
||||||
|
t.Fatal("-s is service command")
|
||||||
|
}
|
||||||
|
if testParseOk(t, "--service", "command").serviceControlAction != "command" {
|
||||||
|
t.Fatal("--service is service command")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseGLInet(t *testing.T) {
|
||||||
|
if testParseOk(t).glinetMode {
|
||||||
|
t.Fatal("empty is not GL-Inet mode")
|
||||||
|
}
|
||||||
|
if !testParseOk(t, "--glinet").glinetMode {
|
||||||
|
t.Fatal("--glinet is GL-Inet mode")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseUnknown(t *testing.T) {
|
||||||
|
testParseErr(t, "unknown word", "x")
|
||||||
|
testParseErr(t, "unknown short", "-x")
|
||||||
|
testParseErr(t, "unknown long", "--x")
|
||||||
|
testParseErr(t, "unknown triple", "---x")
|
||||||
|
testParseErr(t, "unknown plus", "+x")
|
||||||
|
testParseErr(t, "unknown dash", "-")
|
||||||
|
}
|
||||||
|
|
||||||
|
func testSerialize(t *testing.T, o options, ss ...string) {
|
||||||
|
result := serialize(o)
|
||||||
|
if len(result) != len(ss) {
|
||||||
|
t.Fatalf("expected %s but got %s", ss, result)
|
||||||
|
}
|
||||||
|
for i, r := range result {
|
||||||
|
if r != ss[i] {
|
||||||
|
t.Fatalf("expected %s but got %s", ss, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSerializeEmpty(t *testing.T) {
|
||||||
|
testSerialize(t, options{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSerializeConfigFilename(t *testing.T) {
|
||||||
|
testSerialize(t, options{configFilename: "path"}, "-c", "path")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSerializeWorkDir(t *testing.T) {
|
||||||
|
testSerialize(t, options{workDir: "path"}, "-w", "path")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSerializeBindHost(t *testing.T) {
|
||||||
|
testSerialize(t, options{bindHost: "addr"}, "-h", "addr")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSerializeBindPort(t *testing.T) {
|
||||||
|
testSerialize(t, options{bindPort: 666}, "-p", "666")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSerializeLogfile(t *testing.T) {
|
||||||
|
testSerialize(t, options{logFile: "path"}, "-l", "path")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSerializePidfile(t *testing.T) {
|
||||||
|
testSerialize(t, options{pidFile: "path"}, "--pidfile", "path")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSerializeCheckConfig(t *testing.T) {
|
||||||
|
testSerialize(t, options{checkConfig: true}, "--check-config")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSerializeDisableUpdate(t *testing.T) {
|
||||||
|
testSerialize(t, options{disableUpdate: true}, "--no-check-update")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSerializeService(t *testing.T) {
|
||||||
|
testSerialize(t, options{serviceControlAction: "run"}, "-s", "run")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSerializeGLInet(t *testing.T) {
|
||||||
|
testSerialize(t, options{glinetMode: true}, "--glinet")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSerializeMultiple(t *testing.T) {
|
||||||
|
testSerialize(t, options{
|
||||||
|
serviceControlAction: "run",
|
||||||
|
configFilename: "config",
|
||||||
|
workDir: "work",
|
||||||
|
pidFile: "pid",
|
||||||
|
disableUpdate: true,
|
||||||
|
}, "-c", "config", "-w", "work", "-s", "run", "--pidfile", "pid", "--no-check-update")
|
||||||
|
}
|
|
@ -24,12 +24,14 @@ const (
|
||||||
|
|
||||||
// Represents the program that will be launched by a service or daemon
|
// Represents the program that will be launched by a service or daemon
|
||||||
type program struct {
|
type program struct {
|
||||||
|
opts options
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start should quickly start the program
|
// Start should quickly start the program
|
||||||
func (p *program) Start(s service.Service) error {
|
func (p *program) Start(s service.Service) error {
|
||||||
// Start should not block. Do the actual work async.
|
// Start should not block. Do the actual work async.
|
||||||
args := options{runningAsService: true}
|
args := p.opts
|
||||||
|
args.runningAsService = true
|
||||||
go run(args)
|
go run(args)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -125,7 +127,8 @@ func sendSigReload() {
|
||||||
// run - this is a special command that is not supposed to be used directly
|
// run - this is a special command that is not supposed to be used directly
|
||||||
// it is specified when we register a service, and it indicates to the app
|
// it is specified when we register a service, and it indicates to the app
|
||||||
// that it is being run as a service/daemon.
|
// that it is being run as a service/daemon.
|
||||||
func handleServiceControlAction(action string) {
|
func handleServiceControlAction(opts options) {
|
||||||
|
action := opts.serviceControlAction
|
||||||
log.Printf("Service control action: %s", action)
|
log.Printf("Service control action: %s", action)
|
||||||
|
|
||||||
if action == "reload" {
|
if action == "reload" {
|
||||||
|
@ -137,15 +140,17 @@ func handleServiceControlAction(action string) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Unable to find the path to the current directory")
|
log.Fatal("Unable to find the path to the current directory")
|
||||||
}
|
}
|
||||||
|
runOpts := opts
|
||||||
|
runOpts.serviceControlAction = "run"
|
||||||
svcConfig := &service.Config{
|
svcConfig := &service.Config{
|
||||||
Name: serviceName,
|
Name: serviceName,
|
||||||
DisplayName: serviceDisplayName,
|
DisplayName: serviceDisplayName,
|
||||||
Description: serviceDescription,
|
Description: serviceDescription,
|
||||||
WorkingDirectory: pwd,
|
WorkingDirectory: pwd,
|
||||||
Arguments: []string{"-s", "run"},
|
Arguments: serialize(runOpts),
|
||||||
}
|
}
|
||||||
configureService(svcConfig)
|
configureService(svcConfig)
|
||||||
prg := &program{}
|
prg := &program{runOpts}
|
||||||
s, err := service.New(prg, svcConfig)
|
s, err := service.New(prg, svcConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
|
|
Loading…
Reference in New Issue