aboutsummaryrefslogtreecommitdiff
path: root/coremain
diff options
context:
space:
mode:
authorGravatar Miek Gieben <miek@miek.nl> 2016-08-23 16:36:29 +0100
committerGravatar GitHub <noreply@github.com> 2016-08-23 16:36:29 +0100
commit47f4e165a004c10661bb18f5994d283130c93fec (patch)
tree179241bc646aed51219530dd40672c4c7e81fe64 /coremain
parent2153d2defd194d6678b749e54b72b21ed9f4b123 (diff)
downloadcoredns-47f4e165a004c10661bb18f5994d283130c93fec.tar.gz
coredns-47f4e165a004c10661bb18f5994d283130c93fec.tar.zst
coredns-47f4e165a004c10661bb18f5994d283130c93fec.zip
Fix main startup (#232)
Set version and name of the program. And then call coremain.Run(). The coremain split makes CoreDNS embeddable. Also see #189 for an old PR.
Diffstat (limited to 'coremain')
-rw-r--r--coremain/run.go223
-rw-r--r--coremain/run_test.go44
2 files changed, 267 insertions, 0 deletions
diff --git a/coremain/run.go b/coremain/run.go
new file mode 100644
index 000000000..1a5282bc4
--- /dev/null
+++ b/coremain/run.go
@@ -0,0 +1,223 @@
+package coremain
+
+import (
+ "errors"
+ "flag"
+ "fmt"
+ "io/ioutil"
+ "log"
+ "os"
+ "runtime"
+ "strconv"
+ "strings"
+
+ "github.com/mholt/caddy"
+ "gopkg.in/natefinch/lumberjack.v2"
+
+ // Plug in CoreDNS
+ _ "github.com/miekg/coredns/core"
+)
+
+func init() {
+ caddy.TrapSignals()
+ setVersion()
+
+ flag.StringVar(&conf, "conf", "", "Caddyfile to load (default \""+caddy.DefaultConfigFile+"\")")
+ flag.StringVar(&cpu, "cpu", "100%", "CPU cap")
+ flag.BoolVar(&plugins, "plugins", false, "List installed plugins")
+ flag.StringVar(&logfile, "log", "", "Process log file")
+ flag.StringVar(&caddy.PidFile, "pidfile", "", "Path to write pid file")
+ flag.BoolVar(&caddy.Quiet, "quiet", false, "Quiet mode (no initialization output)")
+ flag.BoolVar(&version, "version", false, "Show version")
+
+ caddy.RegisterCaddyfileLoader("flag", caddy.LoaderFunc(confLoader))
+ caddy.SetDefaultCaddyfileLoader("default", caddy.LoaderFunc(defaultLoader))
+}
+
+// Run is CoreDNS's main() function.
+func Run() {
+ flag.Parse()
+
+ caddy.AppName = coreName
+ caddy.AppVersion = coreVersion
+
+ // Set up process log before anything bad happens
+ switch logfile {
+ case "stdout":
+ log.SetOutput(os.Stdout)
+ case "stderr":
+ log.SetOutput(os.Stderr)
+ case "":
+ log.SetOutput(ioutil.Discard)
+ default:
+ log.SetOutput(&lumberjack.Logger{
+ Filename: logfile,
+ MaxSize: 100,
+ MaxAge: 14,
+ MaxBackups: 10,
+ })
+ }
+
+ if version {
+ fmt.Printf("%s-%s\n", caddy.AppName, caddy.AppVersion)
+ if devBuild && gitShortStat != "" {
+ fmt.Printf("%s\n%s\n", gitShortStat, gitFilesModified)
+ }
+ os.Exit(0)
+ }
+ if plugins {
+ fmt.Println(caddy.DescribePlugins())
+ os.Exit(0)
+ }
+
+ // Set CPU cap
+ err := setCPU(cpu)
+ if err != nil {
+ mustLogFatal(err)
+ }
+
+ // Get Caddyfile input
+ caddyfile, err := caddy.LoadCaddyfile(serverType)
+ if err != nil {
+ mustLogFatal(err)
+ }
+
+ // Start your engines
+ instance, err := caddy.Start(caddyfile)
+ if err != nil {
+ mustLogFatal(err)
+ }
+
+ // Twiddle your thumbs
+ instance.Wait()
+}
+
+// mustLogFatal wraps log.Fatal() in a way that ensures the
+// output is always printed to stderr so the user can see it
+// if the user is still there, even if the process log was not
+// enabled. If this process is an upgrade, however, and the user
+// might not be there anymore, this just logs to the process
+// log and exits.
+func mustLogFatal(args ...interface{}) {
+ if !caddy.IsUpgrade() {
+ log.SetOutput(os.Stderr)
+ }
+ log.Fatal(args...)
+}
+
+// confLoader loads the Caddyfile using the -conf flag.
+func confLoader(serverType string) (caddy.Input, error) {
+ if conf == "" {
+ return nil, nil
+ }
+
+ if conf == "stdin" {
+ return caddy.CaddyfileFromPipe(os.Stdin)
+ }
+
+ contents, err := ioutil.ReadFile(conf)
+ if err != nil {
+ return nil, err
+ }
+ return caddy.CaddyfileInput{
+ Contents: contents,
+ Filepath: conf,
+ ServerTypeName: serverType,
+ }, nil
+}
+
+// defaultLoader loads the Caddyfile from the current working directory.
+func defaultLoader(serverType string) (caddy.Input, error) {
+ contents, err := ioutil.ReadFile(caddy.DefaultConfigFile)
+ if err != nil {
+ if os.IsNotExist(err) {
+ return nil, nil
+ }
+ return nil, err
+ }
+ return caddy.CaddyfileInput{
+ Contents: contents,
+ Filepath: caddy.DefaultConfigFile,
+ ServerTypeName: serverType,
+ }, nil
+}
+
+// setVersion figures out the version information
+// based on variables set by -ldflags.
+func setVersion() {
+ // A development build is one that's not at a tag or has uncommitted changes
+ devBuild = gitTag == "" || gitShortStat != ""
+
+ // Only set the appVersion if -ldflags was used
+ if gitNearestTag != "" || gitTag != "" {
+ if devBuild && gitNearestTag != "" {
+ appVersion = fmt.Sprintf("%s (+%s %s)",
+ strings.TrimPrefix(gitNearestTag, "v"), gitCommit, buildDate)
+ } else if gitTag != "" {
+ appVersion = strings.TrimPrefix(gitTag, "v")
+ }
+ }
+}
+
+// setCPU parses string cpu and sets GOMAXPROCS
+// according to its value. It accepts either
+// a number (e.g. 3) or a percent (e.g. 50%).
+func setCPU(cpu string) error {
+ var numCPU int
+
+ availCPU := runtime.NumCPU()
+
+ if strings.HasSuffix(cpu, "%") {
+ // Percent
+ var percent float32
+ pctStr := cpu[:len(cpu)-1]
+ pctInt, err := strconv.Atoi(pctStr)
+ if err != nil || pctInt < 1 || pctInt > 100 {
+ return errors.New("invalid CPU value: percentage must be between 1-100")
+ }
+ percent = float32(pctInt) / 100
+ numCPU = int(float32(availCPU) * percent)
+ } else {
+ // Number
+ num, err := strconv.Atoi(cpu)
+ if err != nil || num < 1 {
+ return errors.New("invalid CPU value: provide a number or percent greater than 0")
+ }
+ numCPU = num
+ }
+
+ if numCPU > availCPU {
+ numCPU = availCPU
+ }
+
+ runtime.GOMAXPROCS(numCPU)
+ return nil
+}
+
+// Flags that control program flow or startup
+var (
+ conf string
+ cpu string
+ logfile string
+ version bool
+ plugins bool
+)
+
+// Build information obtained with the help of -ldflags
+var (
+ appVersion = "(untracked dev build)" // inferred at startup
+ devBuild = true // inferred at startup
+
+ buildDate string // date -u
+ gitTag string // git describe --exact-match HEAD 2> /dev/null
+ gitNearestTag string // git describe --abbrev=0 --tags HEAD
+ gitCommit string // git rev-parse HEAD
+ gitShortStat string // git diff-index --shortstat
+ gitFilesModified string // git diff-index --name-only HEAD
+)
+
+const (
+ coreName = "CoreDNS"
+ coreVersion = "001"
+ serverType = "dns"
+)
diff --git a/coremain/run_test.go b/coremain/run_test.go
new file mode 100644
index 000000000..da01637d8
--- /dev/null
+++ b/coremain/run_test.go
@@ -0,0 +1,44 @@
+package coremain
+
+import (
+ "runtime"
+ "testing"
+)
+
+func TestSetCPU(t *testing.T) {
+ currentCPU := runtime.GOMAXPROCS(-1)
+ maxCPU := runtime.NumCPU()
+ halfCPU := int(0.5 * float32(maxCPU))
+ if halfCPU < 1 {
+ halfCPU = 1
+ }
+ for i, test := range []struct {
+ input string
+ output int
+ shouldErr bool
+ }{
+ {"1", 1, false},
+ {"-1", currentCPU, true},
+ {"0", currentCPU, true},
+ {"100%", maxCPU, false},
+ {"50%", halfCPU, false},
+ {"110%", currentCPU, true},
+ {"-10%", currentCPU, true},
+ {"invalid input", currentCPU, true},
+ {"invalid input%", currentCPU, true},
+ {"9999", maxCPU, false}, // over available CPU
+ } {
+ err := setCPU(test.input)
+ if test.shouldErr && err == nil {
+ t.Errorf("Test %d: Expected error, but there wasn't any", i)
+ }
+ if !test.shouldErr && err != nil {
+ t.Errorf("Test %d: Expected no error, but there was one: %v", i, err)
+ }
+ if actual, expected := runtime.GOMAXPROCS(-1), test.output; actual != expected {
+ t.Errorf("Test %d: GOMAXPROCS was %d but expected %d", i, actual, expected)
+ }
+ // teardown
+ runtime.GOMAXPROCS(currentCPU)
+ }
+}