Repository: caddyserver/caddy (language: go)

Issue:
caddy reload from stdin with default admin address fails on versions >= 2.5.0
After the 2.5.0 update, `caddy reload --config -` fails with the error: `reload: couldn't determine admin API address: adapting config using caddyfile: EOF`

# Reproduce

Here's a little shell script that depends on docker and curl:

```shell
#!/usr/bin/env bash

docker run -d --name caddy -p 8080:80 caddy:2.5.0

sleep 3

echo "--- BEFORE ---"
curl -s localhost:8080 | head -c 16
echo "--------------"

echo -e 'localhost:80\nrespond "hello world"' | docker exec -i caddy caddy reload --config - --adapter caddyfile

echo "--- AFTER ---"
curl -s localhost:8080 | head -c 16
echo "-------------"

docker logs caddy

docker stop caddy
docker rm caddy
```

Both `curl` calls return the same default html (truncated for readability), and reload returns the error above.

Changing the caddy image to `caddy:2.4.6` or lower, or adding `--address localhost:2019` to the reload line results in the second curl returning the expected `hello world`

As far as I can tell, this is due to [`cmdReload`](https://github.com/caddyserver/caddy/blob/8bdee04651d93a5a799f6816ceafaa7ac61fe26d/cmd/commandfuncs.go#L298) calling [`LoadConfig`](https://github.com/caddyserver/caddy/blob/8bdee04651d93a5a799f6816ceafaa7ac61fe26d/cmd/commandfuncs.go#L305) followed by [`DeterminAdminAPIAddress`](https://github.com/caddyserver/caddy/blob/8bdee04651d93a5a799f6816ceafaa7ac61fe26d/cmd/commandfuncs.go#L313), which then [also calls `LoadConfig`](https://github.com/caddyserver/caddy/blob/8bdee04651d93a5a799f6816ceafaa7ac61fe26d/cmd/commandfuncs.go#L747) if `--address` is not specified and the `--config` is.  `LoadConfig` will read stdin successfully the first time, but fail the second.

These changes were introduced [here](https://github.com/caddyserver/caddy/commit/bbad6931e30a2e74b3f53fff797d1115cc9dd491)

# Possible fixes
- Load config outside of DetermineAdminAPIAddress, not sure of the impact this has on other call sites
- Return a more specific error if stdin is used without `--address`.  I don't think this is desired behavior however

Thanks for taking a look!


Discussion / hints:
Did you try with v2.5.2? We had some bugs in v2.5.0 that were resolved in the following versions.
Yes, discovered with 2.5.2, traced it back to 2.5.0

Fix patch:
diff --git a/cmd/commandfuncs.go b/cmd/commandfuncs.go
index b18f509080a..fff8d01736b 100644
--- a/cmd/commandfuncs.go
+++ b/cmd/commandfuncs.go
@@ -280,7 +280,7 @@ func cmdStop(fl Flags) (int, error) {
 	configFlag := fl.String("config")
 	configAdapterFlag := fl.String("adapter")
 
-	adminAddr, err := DetermineAdminAPIAddress(addrFlag, configFlag, configAdapterFlag)
+	adminAddr, err := DetermineAdminAPIAddress(addrFlag, nil, configFlag, configAdapterFlag)
 	if err != nil {
 		return caddy.ExitCodeFailedStartup, fmt.Errorf("couldn't determine admin API address: %v", err)
 	}
@@ -310,7 +310,7 @@ func cmdReload(fl Flags) (int, error) {
 		return caddy.ExitCodeFailedStartup, fmt.Errorf("no config file to load")
 	}
 
-	adminAddr, err := DetermineAdminAPIAddress(addrFlag, configFlag, configAdapterFlag)
+	adminAddr, err := DetermineAdminAPIAddress(addrFlag, config, configFlag, configAdapterFlag)
 	if err != nil {
 		return caddy.ExitCodeFailedStartup, fmt.Errorf("couldn't determine admin API address: %v", err)
 	}
@@ -732,10 +732,11 @@ func AdminAPIRequest(adminAddr, method, uri string, headers http.Header, body io
 
 // 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) {
+// it is returned; if `config` is specified, then that config will be used for
+// finding the admin address; 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 string, config []byte, configFile, configAdapter string) (string, error) {
 	// Prefer the address if specified and non-empty
 	if address != "" {
 		return address, nil
@@ -743,21 +744,29 @@ func DetermineAdminAPIAddress(address, configFile, configAdapter string) (string
 
 	// 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")
+		var loadedConfigFile string
+		var err error
+
+		// use the provided loaded config if non-empty
+		// otherwise, load it from the specified file/adapter
+		loadedConfig := config
+		if len(loadedConfig) == 0 {
+			// get the config in caddy's native format
+			loadedConfig, 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 set
-		if len(config) > 0 {
+		// get the address of the admin listener from the config
+		if len(loadedConfig) > 0 {
 			var tmpStruct struct {
 				Admin caddy.AdminConfig `json:"admin"`
 			}
-			err = json.Unmarshal(config, &tmpStruct)
+			err := json.Unmarshal(loadedConfig, &tmpStruct)
 			if err != nil {
 				return "", fmt.Errorf("unmarshaling admin listener address from config: %v", err)
 			}
diff --git a/modules/caddypki/command.go b/modules/caddypki/command.go
index c26c19ad908..cb86c937965 100644
--- a/modules/caddypki/command.go
+++ b/modules/caddypki/command.go
@@ -113,7 +113,7 @@ func cmdTrust(fl caddycmd.Flags) (int, error) {
 	}
 
 	// Determine where we're sending the request to get the CA info
-	adminAddr, err := caddycmd.DetermineAdminAPIAddress(addrFlag, configFlag, configAdapterFlag)
+	adminAddr, err := caddycmd.DetermineAdminAPIAddress(addrFlag, nil, configFlag, configAdapterFlag)
 	if err != nil {
 		return caddy.ExitCodeFailedStartup, fmt.Errorf("couldn't determine admin API address: %v", err)
 	}
@@ -182,7 +182,7 @@ func cmdUntrust(fl caddycmd.Flags) (int, error) {
 	}
 
 	// Determine where we're sending the request to get the CA info
-	adminAddr, err := caddycmd.DetermineAdminAPIAddress(addrFlag, configFlag, configAdapterFlag)
+	adminAddr, err := caddycmd.DetermineAdminAPIAddress(addrFlag, nil, configFlag, configAdapterFlag)
 	if err != nil {
 		return caddy.ExitCodeFailedStartup, fmt.Errorf("couldn't determine admin API address: %v", err)
 	}


Test patch: