forked from dustin-decker/overseer
-
Notifications
You must be signed in to change notification settings - Fork 2
/
overseer.go
178 lines (165 loc) · 4.77 KB
/
overseer.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
// Package overseer implements daemonizable
// self-upgrading binaries in Go (golang).
package overseer
import (
"errors"
"fmt"
"log"
"os"
"runtime"
"time"
"github.com/jpillora/overseer/fetcher"
)
const (
envChildID = "OVERSEER_SLAVE_ID"
envIsChild = "OVERSEER_IS_SLAVE"
envNumFDs = "OVERSEER_NUM_FDS"
envBinID = "OVERSEER_BIN_ID"
envBinPath = "OVERSEER_BIN_PATH"
envBinCheck = "OVERSEER_BIN_CHECK"
envBinCheckLegacy = "GO_UPGRADE_BIN_CHECK"
)
// Config defines overseer's run-time configuration
type Config struct {
//Required will prevent overseer from fallback to running
//running the program in the main process on failure.
Required bool
//Program's main function
Program func(state State)
//Program's zero-downtime socket listening address (set this or Addresses)
Address string
//Program's zero-downtime socket listening addresses (set this or Address)
Addresses []string
//RestartSignal will manually trigger a graceful restart. Defaults to SIGUSR2.
RestartSignal os.Signal
//TerminateTimeout controls how long overseer should
//wait for the program to terminate itself. After this
//timeout, overseer will issue a SIGKILL.
TerminateTimeout time.Duration
//MinFetchInterval defines the smallest duration between Fetch()s.
//This helps to prevent unwieldy fetch.Interfaces from hogging
//too many resources. Defaults to 1 second.
MinFetchInterval time.Duration
//PreUpgrade runs after a binary has been retrieved, user defined checks
//can be run here and returning an error will cancel the upgrade.
PreUpgrade func(tempBinaryPath string) error
//Debug enables all [updater] logs.
Debug bool
//NoWarn disables warning [updater] logs.
NoWarn bool
//NoRestart disables all restarts, this option essentially converts
//the RestartSignal into a "ShutdownSignal".
NoRestart bool
//NoRestartAfterFetch disables automatic restarts after each upgrade.
//Though manual restarts using the RestartSignal can still be performed.
NoRestartAfterFetch bool
//Fetcher will be used to fetch binaries.
Fetcher fetcher.Interface
//Supervise the child process (ensure it stays running)
Supervise bool
}
func validate(c *Config) error {
//validate
if c.Program == nil {
return errors.New("overseer.Config.Program required")
}
if c.Address != "" {
if len(c.Addresses) > 0 {
return errors.New("overseer.Config.Address and Addresses cant both be set")
}
c.Addresses = []string{c.Address}
} else if len(c.Addresses) > 0 {
c.Address = c.Addresses[0]
}
if c.RestartSignal == nil {
c.RestartSignal = SIGUSR2
}
if c.TerminateTimeout <= 0 {
c.TerminateTimeout = 30 * time.Second
}
if c.MinFetchInterval <= 0 {
c.MinFetchInterval = 1 * time.Second
}
return nil
}
// RunErr allows manual handling of any
// overseer errors.
func RunErr(c Config) error {
return runErr(&c)
}
// Run executes overseer, if an error is
// encountered, overseer fallsback to running
// the program directly (unless Required is set).
func Run(c Config) {
err := runErr(&c)
if err != nil {
if c.Required {
log.Fatalf("[updater] %s", err)
} else if c.Debug || !c.NoWarn {
log.Printf("[updater] disabled. run failed: %s", err)
}
c.Program(DisabledState)
return
}
os.Exit(0)
}
// sanityCheck returns true if a check was performed
func sanityCheck() bool {
//sanity check
if token := os.Getenv(envBinCheck); token != "" {
fmt.Fprint(os.Stdout, token)
return true
}
//legacy sanity check using old env var
if token := os.Getenv(envBinCheckLegacy); token != "" {
fmt.Fprint(os.Stdout, token)
return true
}
return false
}
// SanityCheck manually runs the check to ensure this binary
// is compatible with overseer. This tries to ensure that a restart
// is never performed against a bad binary, as it would require
// manual intervention to rectify. This is automatically done
// on overseer.Run() though it can be manually run prior whenever
// necessary.
func SanityCheck() {
if sanityCheck() {
os.Exit(0)
}
}
// abstraction over parent/child
var currentProcess interface {
triggerRestart()
run() error
}
func runErr(c *Config) error {
//os not supported
if !supported {
return fmt.Errorf("os (%s) not supported", runtime.GOOS)
}
if err := validate(c); err != nil {
return err
}
if sanityCheck() {
return nil
}
//run either in parent or child mode
if os.Getenv(envIsChild) == "1" {
currentProcess = &child{Config: c}
} else {
currentProcess = &parent{Config: c}
}
return currentProcess.run()
}
// Restart programmatically triggers a graceful restart. If NoRestart
// is enabled, then this will essentially be a graceful shutdown.
func Restart() {
if currentProcess != nil {
currentProcess.triggerRestart()
}
}
// IsSupported returns whether overseer is supported on the current OS.
func IsSupported() bool {
return supported
}