Commit b7167803 authored by elcore's avatar elcore Committed by Matt Holt

startupshutdown: is an alias for 'on' (#1880)

parent 97710ced
...@@ -15,12 +15,12 @@ ...@@ -15,12 +15,12 @@
package startupshutdown package startupshutdown
import ( import (
"log" "fmt"
"os"
"os/exec"
"strings" "strings"
"github.com/google/uuid"
"github.com/mholt/caddy" "github.com/mholt/caddy"
"github.com/mholt/caddy/onevent/hook"
) )
func init() { func init() {
...@@ -28,60 +28,68 @@ func init() { ...@@ -28,60 +28,68 @@ func init() {
caddy.RegisterPlugin("shutdown", caddy.Plugin{Action: Shutdown}) caddy.RegisterPlugin("shutdown", caddy.Plugin{Action: Shutdown})
} }
// Startup registers a startup callback to execute during server start. // Startup (an alias for 'on startup') registers a startup callback to execute during server start.
func Startup(c *caddy.Controller) error { func Startup(c *caddy.Controller) error {
return registerCallback(c, c.OnFirstStartup) config, err := onParse(c, caddy.InstanceStartupEvent)
if err != nil {
return c.ArgErr()
}
// Register Event Hooks.
for _, cfg := range config {
caddy.RegisterEventHook("on-"+cfg.ID, cfg.Hook)
}
fmt.Println("NOTICE: Startup directive will be removed in a later version. Please migrate to 'on startup'")
return nil
} }
// Shutdown registers a shutdown callback to execute during server stop. // Shutdown (an alias for 'on shutdown') registers a shutdown callback to execute during server start.
func Shutdown(c *caddy.Controller) error { func Shutdown(c *caddy.Controller) error {
return registerCallback(c, c.OnFinalShutdown) config, err := onParse(c, caddy.ShutdownEvent)
if err != nil {
return c.ArgErr()
}
// Register Event Hooks.
for _, cfg := range config {
caddy.RegisterEventHook("on-"+cfg.ID, cfg.Hook)
}
fmt.Println("NOTICE: Shutdown directive will be removed in a later version. Please migrate to 'on shutdown'")
return nil
} }
// registerCallback registers a callback function to execute by func onParse(c *caddy.Controller, event caddy.EventName) ([]*hook.Config, error) {
// using c to parse the directive. It registers the callback var config []*hook.Config
// to be executed using registerFunc.
func registerCallback(c *caddy.Controller, registerFunc func(func() error)) error {
var funcs []func() error
for c.Next() { for c.Next() {
cfg := new(hook.Config)
args := c.RemainingArgs() args := c.RemainingArgs()
if len(args) == 0 { if len(args) == 0 {
return c.ArgErr() return config, c.ArgErr()
} }
nonblock := false // Configure Event.
if len(args) > 1 && args[len(args)-1] == "&" { cfg.Event = event
// Run command in background; non-blocking
nonblock = true
args = args[:len(args)-1]
}
// Assign an unique ID.
cfg.ID = uuid.New().String()
// Extract command and arguments.
command, args, err := caddy.SplitCommandAndArgs(strings.Join(args, " ")) command, args, err := caddy.SplitCommandAndArgs(strings.Join(args, " "))
if err != nil { if err != nil {
return c.Err(err.Error()) return config, c.Err(err.Error())
} }
fn := func() error { cfg.Command = command
cmd := exec.Command(command, args...) cfg.Args = args
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if nonblock {
log.Printf("[INFO] Nonblocking Command:\"%s %s\"", command, strings.Join(args, " "))
return cmd.Start()
}
log.Printf("[INFO] Blocking Command:\"%s %s\"", command, strings.Join(args, " "))
return cmd.Run()
}
funcs = append(funcs, fn) config = append(config, cfg)
} }
return c.OncePerServerBlock(func() error { return config, nil
for _, fn := range funcs {
registerFunc(fn)
}
return nil
})
} }
...@@ -15,66 +15,55 @@ ...@@ -15,66 +15,55 @@
package startupshutdown package startupshutdown
import ( import (
"os"
"path/filepath"
"strconv"
"testing" "testing"
"time"
"github.com/mholt/caddy" "github.com/mholt/caddy"
) )
// The Startup function's tests are symmetrical to Shutdown tests,
// because the Startup and Shutdown functions share virtually the
// same functionality
func TestStartup(t *testing.T) { func TestStartup(t *testing.T) {
tempDirPath := os.TempDir() tests := []struct {
name string
input string
shouldErr bool
}{
{name: "noInput", input: "startup", shouldErr: true},
{name: "startup", input: "startup cmd arg", shouldErr: false},
}
testDir := filepath.Join(tempDirPath, "temp_dir_for_testing_startupshutdown") for _, test := range tests {
defer func() { t.Run(test.name, func(t *testing.T) {
// clean up after non-blocking startup function quits c := caddy.NewTestController("", test.input)
time.Sleep(500 * time.Millisecond)
os.RemoveAll(testDir)
}()
osSenitiveTestDir := filepath.FromSlash(testDir)
os.RemoveAll(osSenitiveTestDir) // start with a clean slate
var registeredFunction func() error err := Startup(c)
fakeRegister := func(fn func() error) { if err == nil && test.shouldErr {
registeredFunction = fn t.Error("Test didn't error, but it should have")
} else if err != nil && !test.shouldErr {
t.Errorf("Test errored, but it shouldn't have; got '%v'", err)
}
})
} }
}
func TestShutdown(t *testing.T) {
tests := []struct { tests := []struct {
input string name string
shouldExecutionErr bool input string
shouldRemoveErr bool shouldErr bool
}{ }{
// test case #0 tests proper functionality blocking commands {name: "noInput", input: "shutdown", shouldErr: true},
{"startup mkdir " + osSenitiveTestDir, false, false}, {name: "shutdown", input: "shutdown cmd arg", shouldErr: false},
// test case #1 tests proper functionality of non-blocking commands
{"startup mkdir " + osSenitiveTestDir + " &", false, true},
// test case #2 tests handling of non-existent commands
{"startup " + strconv.Itoa(int(time.Now().UnixNano())), true, true},
} }
for i, test := range tests { for _, test := range tests {
c := caddy.NewTestController("", test.input) t.Run(test.name, func(t *testing.T) {
err := registerCallback(c, fakeRegister) c := caddy.NewTestController("", test.input)
if err != nil {
t.Errorf("Expected no errors, got: %v", err) err := Shutdown(c)
} if err == nil && test.shouldErr {
if registeredFunction == nil { t.Error("Test didn't error, but it should have")
t.Fatalf("Expected function to be registered, but it wasn't") } else if err != nil && !test.shouldErr {
} t.Errorf("Test errored, but it shouldn't have; got '%v'", err)
err = registeredFunction() }
if err != nil && !test.shouldExecutionErr { })
t.Errorf("Test %d received an error of:\n%v", i, err)
}
err = os.Remove(osSenitiveTestDir)
if err != nil && !test.shouldRemoveErr {
t.Errorf("Test %d received an error of:\n%v", i, err)
}
} }
} }
\ No newline at end of file
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment