diff options
Diffstat (limited to 'cmd/commandfuncs.go')
| -rw-r--r-- | cmd/commandfuncs.go | 112 | 
1 files changed, 74 insertions, 38 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 {  | 
