summaryrefslogtreecommitdiff
path: root/cmd
diff options
context:
space:
mode:
Diffstat (limited to 'cmd')
-rw-r--r--cmd/commandfuncs.go112
-rw-r--r--cmd/commands.go7
-rw-r--r--cmd/main.go8
3 files changed, 83 insertions, 44 deletions
diff --git a/cmd/commandfuncs.go b/cmd/commandfuncs.go
index ec56ab9..d308aeb 100644
--- a/cmd/commandfuncs.go
+++ b/cmd/commandfuncs.go
@@ -202,7 +202,7 @@ func cmdRun(fl Flags) (int, error) {
// we don't use 'else' here since this value might have been changed in 'if' block; i.e. not mutually exclusive
var configFile string
if !runCmdResumeFlag {
- config, configFile, err = loadConfig(runCmdConfigFlag, runCmdConfigAdapterFlag)
+ config, configFile, err = LoadConfig(runCmdConfigFlag, runCmdConfigAdapterFlag)
if err != nil {
return caddy.ExitCodeFailedStartup, err
}
@@ -275,25 +275,33 @@ func cmdRun(fl Flags) (int, error) {
}
func cmdStop(fl Flags) (int, error) {
- stopCmdAddrFlag := fl.String("address")
+ addrFlag := fl.String("address")
+ configFlag := fl.String("config")
+ configAdapterFlag := fl.String("adapter")
- err := apiRequest(stopCmdAddrFlag, http.MethodPost, "/stop", nil, nil)
+ adminAddr, err := DetermineAdminAPIAddress(addrFlag, configFlag, configAdapterFlag)
+ if err != nil {
+ return caddy.ExitCodeFailedStartup, fmt.Errorf("couldn't determine admin API address: %v", err)
+ }
+
+ resp, err := AdminAPIRequest(adminAddr, http.MethodPost, "/stop", nil, nil)
if err != nil {
caddy.Log().Warn("failed using API to stop instance", zap.Error(err))
return caddy.ExitCodeFailedStartup, err
}
+ defer resp.Body.Close()
return caddy.ExitCodeSuccess, nil
}
func cmdReload(fl Flags) (int, error) {
- reloadCmdConfigFlag := fl.String("config")
- reloadCmdConfigAdapterFlag := fl.String("adapter")
- reloadCmdAddrFlag := fl.String("address")
- reloadCmdForceFlag := fl.Bool("force")
+ configFlag := fl.String("config")
+ configAdapterFlag := fl.String("adapter")
+ addrFlag := fl.String("address")
+ forceFlag := fl.Bool("force")
// get the config in caddy's native format
- config, configFile, err := loadConfig(reloadCmdConfigFlag, reloadCmdConfigAdapterFlag)
+ config, configFile, err := LoadConfig(configFlag, configAdapterFlag)
if err != nil {
return caddy.ExitCodeFailedStartup, err
}
@@ -301,30 +309,22 @@ func cmdReload(fl Flags) (int, error) {
return caddy.ExitCodeFailedStartup, fmt.Errorf("no config file to load")
}
- // get the address of the admin listener; use flag if specified
- adminAddr := reloadCmdAddrFlag
- if adminAddr == "" && len(config) > 0 {
- var tmpStruct struct {
- Admin caddy.AdminConfig `json:"admin"`
- }
- err = json.Unmarshal(config, &tmpStruct)
- if err != nil {
- return caddy.ExitCodeFailedStartup,
- fmt.Errorf("unmarshaling admin listener address from config: %v", err)
- }
- adminAddr = tmpStruct.Admin.Listen
+ adminAddr, err := DetermineAdminAPIAddress(addrFlag, configFlag, configAdapterFlag)
+ if err != nil {
+ return caddy.ExitCodeFailedStartup, fmt.Errorf("couldn't determine admin API address: %v", err)
}
// optionally force a config reload
headers := make(http.Header)
- if reloadCmdForceFlag {
+ if forceFlag {
headers.Set("Cache-Control", "must-revalidate")
}
- err = apiRequest(adminAddr, http.MethodPost, "/load", headers, bytes.NewReader(config))
+ resp, err := AdminAPIRequest(adminAddr, http.MethodPost, "/load", headers, bytes.NewReader(config))
if err != nil {
return caddy.ExitCodeFailedStartup, fmt.Errorf("sending configuration to instance: %v", err)
}
+ defer resp.Body.Close()
return caddy.ExitCodeSuccess, nil
}
@@ -518,7 +518,7 @@ func cmdValidateConfig(fl Flags) (int, error) {
validateCmdConfigFlag := fl.String("config")
validateCmdAdapterFlag := fl.String("adapter")
- input, _, err := loadConfig(validateCmdConfigFlag, validateCmdAdapterFlag)
+ input, _, err := LoadConfig(validateCmdConfigFlag, validateCmdAdapterFlag)
if err != nil {
return caddy.ExitCodeFailedStartup, err
}
@@ -640,17 +640,15 @@ commands:
return caddy.ExitCodeSuccess, nil
}
-// apiRequest makes an API request to the endpoint adminAddr with the
-// given HTTP method and request URI. If body is non-nil, it will be
-// assumed to be Content-Type application/json.
-func apiRequest(adminAddr, method, uri string, headers http.Header, body io.Reader) error {
- // parse the admin address
- if adminAddr == "" {
- adminAddr = caddy.DefaultAdminListen
- }
+// AdminAPIRequest makes an API request according to the CLI flags given,
+// with the given HTTP method and request URI. If body is non-nil, it will
+// be assumed to be Content-Type application/json. The caller should close
+// the response body. Should only be used by Caddy CLI commands which
+// need to interact with a running instance of Caddy via the admin API.
+func AdminAPIRequest(adminAddr, method, uri string, headers http.Header, body io.Reader) (*http.Response, error) {
parsedAddr, err := caddy.ParseNetworkAddress(adminAddr)
if err != nil || parsedAddr.PortRangeSize() > 1 {
- return fmt.Errorf("invalid admin address %s: %v", adminAddr, err)
+ return nil, fmt.Errorf("invalid admin address %s: %v", adminAddr, err)
}
origin := parsedAddr.JoinHostPort(0)
if parsedAddr.IsUnixNetwork() {
@@ -660,7 +658,7 @@ func apiRequest(adminAddr, method, uri string, headers http.Header, body io.Read
// form the request
req, err := http.NewRequest(method, "http://"+origin+uri, body)
if err != nil {
- return fmt.Errorf("making request: %v", err)
+ return nil, fmt.Errorf("making request: %v", err)
}
if parsedAddr.IsUnixNetwork() {
// When listening on a unix socket, the admin endpoint doesn't
@@ -700,20 +698,58 @@ func apiRequest(adminAddr, method, uri string, headers http.Header, body io.Read
resp, err := client.Do(req)
if err != nil {
- return fmt.Errorf("performing request: %v", err)
+ return nil, fmt.Errorf("performing request: %v", err)
}
- defer resp.Body.Close()
// if it didn't work, let the user know
if resp.StatusCode >= 400 {
respBody, err := io.ReadAll(io.LimitReader(resp.Body, 1024*10))
if err != nil {
- return fmt.Errorf("HTTP %d: reading error message: %v", resp.StatusCode, err)
+ return nil, fmt.Errorf("HTTP %d: reading error message: %v", resp.StatusCode, err)
+ }
+ return nil, fmt.Errorf("caddy responded with error: HTTP %d: %s", resp.StatusCode, respBody)
+ }
+
+ return resp, nil
+}
+
+// DetermineAdminAPIAddress determines which admin API endpoint address should
+// be used based on the inputs. By priority: if `address` is specified, then
+// it is returned; if `configFile` (and `configAdapter`) are specified, then that
+// config will be loaded to find the admin address; otherwise, the default
+// admin listen address will be returned.
+func DetermineAdminAPIAddress(address, configFile, configAdapter string) (string, error) {
+ // Prefer the address if specified and non-empty
+ if address != "" {
+ return address, nil
+ }
+
+ // Try to load the config from file if specified, with the given adapter name
+ if configFile != "" {
+ // get the config in caddy's native format
+ config, loadedConfigFile, err := LoadConfig(configFile, configAdapter)
+ if err != nil {
+ return "", err
+ }
+ if loadedConfigFile == "" {
+ return "", fmt.Errorf("no config file to load")
+ }
+
+ // get the address of the admin listener
+ if len(config) > 0 {
+ var tmpStruct struct {
+ Admin caddy.AdminConfig `json:"admin"`
+ }
+ err = json.Unmarshal(config, &tmpStruct)
+ if err != nil {
+ return "", fmt.Errorf("unmarshaling admin listener address from config: %v", err)
+ }
+ return tmpStruct.Admin.Listen, nil
}
- return fmt.Errorf("caddy responded with error: HTTP %d: %s", resp.StatusCode, respBody)
}
- return nil
+ // Fallback to the default listen address otherwise
+ return caddy.DefaultAdminListen, nil
}
type moduleInfo struct {
diff --git a/cmd/commands.go b/cmd/commands.go
index 1e2c40d..0c68b7c 100644
--- a/cmd/commands.go
+++ b/cmd/commands.go
@@ -156,16 +156,19 @@ development environment.`,
RegisterCommand(Command{
Name: "stop",
Func: cmdStop,
+ Usage: "[--address <interface>] [--config <path> [--adapter <name>]]",
Short: "Gracefully stops a started Caddy process",
Long: `
Stops the background Caddy process as gracefully as possible.
It requires that the admin API is enabled and accessible, since it will
-use the API's /stop endpoint. The address of this request can be
-customized using the --address flag if it is not the default.`,
+use the API's /stop endpoint. The address of this request can be customized
+using the --address flag, or from the given --config, if not the default.`,
Flags: func() *flag.FlagSet {
fs := flag.NewFlagSet("stop", flag.ExitOnError)
fs.String("address", "", "The address to use to reach the admin API endpoint, if not the default")
+ fs.String("config", "", "Configuration file to use to parse the admin address, if --address is not used")
+ fs.String("adapter", "", "Name of config adapter to apply (when --config is used)")
return fs
}(),
})
diff --git a/cmd/main.go b/cmd/main.go
index 7c33c55..f111ba4 100644
--- a/cmd/main.go
+++ b/cmd/main.go
@@ -103,15 +103,15 @@ func handlePingbackConn(conn net.Conn, expect []byte) error {
return nil
}
-// loadConfig loads the config from configFile and adapts it
+// LoadConfig loads the config from configFile and adapts it
// using adapterName. If adapterName is specified, configFile
// must be also. If no configFile is specified, it tries
// loading a default config file. The lack of a config file is
// not treated as an error, but false will be returned if
// there is no config available. It prints any warnings to stderr,
// and returns the resulting JSON config bytes along with
-// whether a config file was loaded or not.
-func loadConfig(configFile, adapterName string) ([]byte, string, error) {
+// the name of the loaded config file (if any).
+func LoadConfig(configFile, adapterName string) ([]byte, string, error) {
// specifying an adapter without a config file is ambiguous
if adapterName != "" && configFile == "" {
return nil, "", fmt.Errorf("cannot adapt config without config file (use --config)")
@@ -262,7 +262,7 @@ func watchConfigFile(filename, adapterName string) {
lastModified = info.ModTime()
// load the contents of the file
- config, _, err := loadConfig(filename, adapterName)
+ config, _, err := LoadConfig(filename, adapterName)
if err != nil {
logger().Error("unable to load latest config", zap.Error(err))
continue