mirror of
				https://github.com/caddyserver/caddy.git
				synced 2025-11-01 19:17:25 -04:00 
			
		
		
		
	cmd: Refactor subcommands, add help, make them pluggable
* cli: Change command structure, add help subcommand (#328) * cli: improve subcommand structure - make help command as normal subcommand - add flag usage message for each command * cmd: Refactor subcommands and command line help; make commands pluggable
This commit is contained in:
		
							parent
							
								
									c95db3551d
								
							
						
					
					
						commit
						0006df6026
					
				
							
								
								
									
										391
									
								
								cmd/commandfuncs.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										391
									
								
								cmd/commandfuncs.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,391 @@ | ||||
| // Copyright 2015 Matthew Holt and The Caddy Authors | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| // you may not use this file except in compliance with the License. | ||||
| // You may obtain a copy of the License at | ||||
| // | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| // See the License for the specific language governing permissions and | ||||
| // limitations under the License. | ||||
| 
 | ||||
| package caddycmd | ||||
| 
 | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"crypto/rand" | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"io/ioutil" | ||||
| 	"log" | ||||
| 	"net" | ||||
| 	"net/http" | ||||
| 	"os" | ||||
| 	"os/exec" | ||||
| 	"strings" | ||||
| 
 | ||||
| 	"github.com/caddyserver/caddy/v2" | ||||
| 	"github.com/caddyserver/caddy/v2/caddyconfig" | ||||
| 	"github.com/keybase/go-ps" | ||||
| 	"github.com/mholt/certmagic" | ||||
| ) | ||||
| 
 | ||||
| func cmdStart(fl Flags) (int, error) { | ||||
| 	startCmdConfigFlag := fl.String("config") | ||||
| 	startCmdConfigAdapterFlag := fl.String("config-adapter") | ||||
| 
 | ||||
| 	// open a listener to which the child process will connect when | ||||
| 	// it is ready to confirm that it has successfully started | ||||
| 	ln, err := net.Listen("tcp", "127.0.0.1:0") | ||||
| 	if err != nil { | ||||
| 		return caddy.ExitCodeFailedStartup, | ||||
| 			fmt.Errorf("opening listener for success confirmation: %v", err) | ||||
| 	} | ||||
| 	defer ln.Close() | ||||
| 
 | ||||
| 	// craft the command with a pingback address and with a | ||||
| 	// pipe for its stdin, so we can tell it our confirmation | ||||
| 	// code that we expect so that some random port scan at | ||||
| 	// the most unfortunate time won't fool us into thinking | ||||
| 	// the child succeeded (i.e. the alternative is to just | ||||
| 	// wait for any connection on our listener, but better to | ||||
| 	// ensure it's the process we're expecting - we can be | ||||
| 	// sure by giving it some random bytes and having it echo | ||||
| 	// them back to us) | ||||
| 	cmd := exec.Command(os.Args[0], "run", "--pingback", ln.Addr().String()) | ||||
| 	if startCmdConfigFlag != "" { | ||||
| 		cmd.Args = append(cmd.Args, "--config", startCmdConfigFlag) | ||||
| 	} | ||||
| 	if startCmdConfigAdapterFlag != "" { | ||||
| 		cmd.Args = append(cmd.Args, "--config-adapter", startCmdConfigAdapterFlag) | ||||
| 	} | ||||
| 	stdinpipe, err := cmd.StdinPipe() | ||||
| 	if err != nil { | ||||
| 		return caddy.ExitCodeFailedStartup, | ||||
| 			fmt.Errorf("creating stdin pipe: %v", err) | ||||
| 	} | ||||
| 	cmd.Stdout = os.Stdout | ||||
| 	cmd.Stderr = os.Stderr | ||||
| 
 | ||||
| 	// generate the random bytes we'll send to the child process | ||||
| 	expect := make([]byte, 32) | ||||
| 	_, err = rand.Read(expect) | ||||
| 	if err != nil { | ||||
| 		return caddy.ExitCodeFailedStartup, fmt.Errorf("generating random confirmation bytes: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	// begin writing the confirmation bytes to the child's | ||||
| 	// stdin; use a goroutine since the child hasn't been | ||||
| 	// started yet, and writing sychronously would result | ||||
| 	// in a deadlock | ||||
| 	go func() { | ||||
| 		stdinpipe.Write(expect) | ||||
| 		stdinpipe.Close() | ||||
| 	}() | ||||
| 
 | ||||
| 	// start the process | ||||
| 	err = cmd.Start() | ||||
| 	if err != nil { | ||||
| 		return caddy.ExitCodeFailedStartup, fmt.Errorf("starting caddy process: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	// there are two ways we know we're done: either | ||||
| 	// the process will connect to our listener, or | ||||
| 	// it will exit with an error | ||||
| 	success, exit := make(chan struct{}), make(chan error) | ||||
| 
 | ||||
| 	// in one goroutine, we await the success of the child process | ||||
| 	go func() { | ||||
| 		for { | ||||
| 			conn, err := ln.Accept() | ||||
| 			if err != nil { | ||||
| 				if !strings.Contains(err.Error(), "use of closed network connection") { | ||||
| 					log.Println(err) | ||||
| 				} | ||||
| 				break | ||||
| 			} | ||||
| 			err = handlePingbackConn(conn, expect) | ||||
| 			if err == nil { | ||||
| 				close(success) | ||||
| 				break | ||||
| 			} | ||||
| 			log.Println(err) | ||||
| 		} | ||||
| 	}() | ||||
| 
 | ||||
| 	// in another goroutine, we await the failure of the child process | ||||
| 	go func() { | ||||
| 		err := cmd.Wait() // don't send on this line! Wait blocks, but send starts before it unblocks | ||||
| 		exit <- err       // sending on separate line ensures select won't trigger until after Wait unblocks | ||||
| 	}() | ||||
| 
 | ||||
| 	// when one of the goroutines unblocks, we're done and can exit | ||||
| 	select { | ||||
| 	case <-success: | ||||
| 		fmt.Printf("Successfully started Caddy (pid=%d)\n", cmd.Process.Pid) | ||||
| 	case err := <-exit: | ||||
| 		return caddy.ExitCodeFailedStartup, | ||||
| 			fmt.Errorf("caddy process exited with error: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	return caddy.ExitCodeSuccess, nil | ||||
| } | ||||
| 
 | ||||
| func cmdRun(fl Flags) (int, error) { | ||||
| 	runCmdConfigFlag := fl.String("config") | ||||
| 	runCmdConfigAdapterFlag := fl.String("config-adapter") | ||||
| 	runCmdPrintEnvFlag := fl.Bool("print-env") | ||||
| 	runCmdPingbackFlag := fl.String("pingback") | ||||
| 
 | ||||
| 	// if we are supposed to print the environment, do that first | ||||
| 	if runCmdPrintEnvFlag { | ||||
| 		printEnvironment() | ||||
| 	} | ||||
| 
 | ||||
| 	// get the config in caddy's native format | ||||
| 	config, err := loadConfig(runCmdConfigFlag, runCmdConfigAdapterFlag) | ||||
| 	if err != nil { | ||||
| 		return caddy.ExitCodeFailedStartup, err | ||||
| 	} | ||||
| 
 | ||||
| 	// set a fitting User-Agent for ACME requests | ||||
| 	goModule := caddy.GoModule() | ||||
| 	cleanModVersion := strings.TrimPrefix(goModule.Version, "v") | ||||
| 	certmagic.UserAgent = "Caddy/" + cleanModVersion | ||||
| 
 | ||||
| 	// start the admin endpoint along with any initial config | ||||
| 	err = caddy.StartAdmin(config) | ||||
| 	if err != nil { | ||||
| 		return caddy.ExitCodeFailedStartup, | ||||
| 			fmt.Errorf("starting caddy administration endpoint: %v", err) | ||||
| 	} | ||||
| 	defer caddy.StopAdmin() | ||||
| 
 | ||||
| 	// if we are to report to another process the successful start | ||||
| 	// of the server, do so now by echoing back contents of stdin | ||||
| 	if runCmdPingbackFlag != "" { | ||||
| 		confirmationBytes, err := ioutil.ReadAll(os.Stdin) | ||||
| 		if err != nil { | ||||
| 			return caddy.ExitCodeFailedStartup, | ||||
| 				fmt.Errorf("reading confirmation bytes from stdin: %v", err) | ||||
| 		} | ||||
| 		conn, err := net.Dial("tcp", runCmdPingbackFlag) | ||||
| 		if err != nil { | ||||
| 			return caddy.ExitCodeFailedStartup, | ||||
| 				fmt.Errorf("dialing confirmation address: %v", err) | ||||
| 		} | ||||
| 		defer conn.Close() | ||||
| 		_, err = conn.Write(confirmationBytes) | ||||
| 		if err != nil { | ||||
| 			return caddy.ExitCodeFailedStartup, | ||||
| 				fmt.Errorf("writing confirmation bytes to %s: %v", runCmdPingbackFlag, err) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	select {} | ||||
| } | ||||
| 
 | ||||
| func cmdStop(_ Flags) (int, error) { | ||||
| 	processList, err := ps.Processes() | ||||
| 	if err != nil { | ||||
| 		return caddy.ExitCodeFailedStartup, fmt.Errorf("listing processes: %v", err) | ||||
| 	} | ||||
| 	thisProcName := getProcessName() | ||||
| 	var found bool | ||||
| 	for _, p := range processList { | ||||
| 		// the process we're looking for should have the same name but different PID | ||||
| 		if p.Executable() == thisProcName && p.Pid() != os.Getpid() { | ||||
| 			found = true | ||||
| 			fmt.Printf("pid=%d\n", p.Pid()) | ||||
| 
 | ||||
| 			if err := gracefullyStopProcess(p.Pid()); err != nil { | ||||
| 				return caddy.ExitCodeFailedStartup, err | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	if !found { | ||||
| 		return caddy.ExitCodeFailedStartup, fmt.Errorf("Caddy is not running") | ||||
| 	} | ||||
| 	fmt.Println(" success") | ||||
| 	return caddy.ExitCodeSuccess, nil | ||||
| } | ||||
| 
 | ||||
| func cmdReload(fl Flags) (int, error) { | ||||
| 	reloadCmdConfigFlag := fl.String("config") | ||||
| 	reloadCmdConfigAdapterFlag := fl.String("config-adapter") | ||||
| 	reloadCmdAddrFlag := fl.String("address") | ||||
| 
 | ||||
| 	// a configuration is required | ||||
| 	if reloadCmdConfigFlag == "" { | ||||
| 		return caddy.ExitCodeFailedStartup, | ||||
| 			fmt.Errorf("no configuration to load (use --config)") | ||||
| 	} | ||||
| 
 | ||||
| 	// get the config in caddy's native format | ||||
| 	config, err := loadConfig(reloadCmdConfigFlag, reloadCmdConfigAdapterFlag) | ||||
| 	if err != nil { | ||||
| 		return caddy.ExitCodeFailedStartup, err | ||||
| 	} | ||||
| 
 | ||||
| 	// get the address of the admin listener and craft endpoint URL | ||||
| 	adminAddr := reloadCmdAddrFlag | ||||
| 	if adminAddr == "" { | ||||
| 		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 | ||||
| 	} | ||||
| 	if adminAddr == "" { | ||||
| 		adminAddr = caddy.DefaultAdminListen | ||||
| 	} | ||||
| 	adminEndpoint := fmt.Sprintf("http://%s/load", adminAddr) | ||||
| 
 | ||||
| 	// send the configuration to the instance | ||||
| 	resp, err := http.Post(adminEndpoint, "application/json", bytes.NewReader(config)) | ||||
| 	if err != nil { | ||||
| 		return caddy.ExitCodeFailedStartup, | ||||
| 			fmt.Errorf("sending configuration to instance: %v", err) | ||||
| 	} | ||||
| 	defer resp.Body.Close() | ||||
| 
 | ||||
| 	// if it didn't work, let the user know | ||||
| 	if resp.StatusCode >= 400 { | ||||
| 		respBody, err := ioutil.ReadAll(io.LimitReader(resp.Body, 1024*10)) | ||||
| 		if err != nil { | ||||
| 			return caddy.ExitCodeFailedStartup, | ||||
| 				fmt.Errorf("HTTP %d: reading error message: %v", resp.StatusCode, err) | ||||
| 		} | ||||
| 		return caddy.ExitCodeFailedStartup, | ||||
| 			fmt.Errorf("caddy responded with error: HTTP %d: %s", resp.StatusCode, respBody) | ||||
| 	} | ||||
| 
 | ||||
| 	return caddy.ExitCodeSuccess, nil | ||||
| } | ||||
| 
 | ||||
| func cmdVersion(_ Flags) (int, error) { | ||||
| 	goModule := caddy.GoModule() | ||||
| 	if goModule.Sum != "" { | ||||
| 		// a build with a known version will also have a checksum | ||||
| 		fmt.Printf("%s %s\n", goModule.Version, goModule.Sum) | ||||
| 	} else { | ||||
| 		fmt.Println(goModule.Version) | ||||
| 	} | ||||
| 	return caddy.ExitCodeSuccess, nil | ||||
| } | ||||
| 
 | ||||
| func cmdListModules(_ Flags) (int, error) { | ||||
| 	for _, m := range caddy.Modules() { | ||||
| 		fmt.Println(m) | ||||
| 	} | ||||
| 	return caddy.ExitCodeSuccess, nil | ||||
| } | ||||
| 
 | ||||
| func cmdEnviron(_ Flags) (int, error) { | ||||
| 	printEnvironment() | ||||
| 	return caddy.ExitCodeSuccess, nil | ||||
| } | ||||
| 
 | ||||
| func cmdAdaptConfig(fl Flags) (int, error) { | ||||
| 	adaptCmdAdapterFlag := fl.String("adapter") | ||||
| 	adaptCmdInputFlag := fl.String("input") | ||||
| 	adaptCmdPrettyFlag := fl.Bool("pretty") | ||||
| 
 | ||||
| 	if adaptCmdAdapterFlag == "" || adaptCmdInputFlag == "" { | ||||
| 		return caddy.ExitCodeFailedStartup, | ||||
| 			fmt.Errorf("usage: caddy adapt-config --adapter <name> --input <file>") | ||||
| 	} | ||||
| 
 | ||||
| 	cfgAdapter := caddyconfig.GetAdapter(adaptCmdAdapterFlag) | ||||
| 	if cfgAdapter == nil { | ||||
| 		return caddy.ExitCodeFailedStartup, | ||||
| 			fmt.Errorf("unrecognized config adapter: %s", adaptCmdAdapterFlag) | ||||
| 	} | ||||
| 
 | ||||
| 	input, err := ioutil.ReadFile(adaptCmdInputFlag) | ||||
| 	if err != nil { | ||||
| 		return caddy.ExitCodeFailedStartup, | ||||
| 			fmt.Errorf("reading input file: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	opts := make(map[string]interface{}) | ||||
| 	if adaptCmdPrettyFlag { | ||||
| 		opts["pretty"] = "true" | ||||
| 	} | ||||
| 
 | ||||
| 	adaptedConfig, warnings, err := cfgAdapter.Adapt(input, opts) | ||||
| 	if err != nil { | ||||
| 		return caddy.ExitCodeFailedStartup, err | ||||
| 	} | ||||
| 
 | ||||
| 	// print warnings to stderr | ||||
| 	for _, warn := range warnings { | ||||
| 		msg := warn.Message | ||||
| 		if warn.Directive != "" { | ||||
| 			msg = fmt.Sprintf("%s: %s", warn.Directive, warn.Message) | ||||
| 		} | ||||
| 		log.Printf("[WARNING][%s] %s:%d: %s", adaptCmdAdapterFlag, warn.File, warn.Line, msg) | ||||
| 	} | ||||
| 
 | ||||
| 	// print result to stdout | ||||
| 	fmt.Println(string(adaptedConfig)) | ||||
| 
 | ||||
| 	return caddy.ExitCodeSuccess, nil | ||||
| } | ||||
| 
 | ||||
| func cmdHelp(fl Flags) (int, error) { | ||||
| 	const fullDocs = `Full documentation is available at: | ||||
| https://github.com/caddyserver/caddy/wiki/v2:-Documentation` | ||||
| 
 | ||||
| 	args := fl.Args() | ||||
| 	if len(args) == 0 { | ||||
| 		s := `Caddy is an extensible server platform. | ||||
| 
 | ||||
| usage: | ||||
|   caddy <command> [<args...>] | ||||
| 
 | ||||
| commands: | ||||
| ` | ||||
| 		for _, cmd := range commands { | ||||
| 			s += fmt.Sprintf("  %-15s %s\n", cmd.Name, cmd.Short) | ||||
| 		} | ||||
| 
 | ||||
| 		s += "\nUse 'caddy help <command>' for more information about a command.\n" | ||||
| 		s += "\n" + fullDocs + "\n" | ||||
| 
 | ||||
| 		fmt.Print(s) | ||||
| 
 | ||||
| 		return caddy.ExitCodeSuccess, nil | ||||
| 	} else if len(args) > 1 { | ||||
| 		return caddy.ExitCodeFailedStartup, fmt.Errorf("can only give help with one command") | ||||
| 	} | ||||
| 
 | ||||
| 	subcommand, ok := commands[args[0]] | ||||
| 	if !ok { | ||||
| 		return caddy.ExitCodeFailedStartup, fmt.Errorf("unknown command: %s", args[0]) | ||||
| 	} | ||||
| 
 | ||||
| 	result := fmt.Sprintf("%s\n\nusage:\n  caddy %s %s\n", | ||||
| 		strings.TrimSpace(subcommand.Long), | ||||
| 		subcommand.Name, | ||||
| 		strings.TrimSpace(subcommand.Usage), | ||||
| 	) | ||||
| 
 | ||||
| 	if help := flagHelp(subcommand.Flags); help != "" { | ||||
| 		result += fmt.Sprintf("\nflags:\n%s", help) | ||||
| 	} | ||||
| 
 | ||||
| 	result += "\n" + fullDocs + "\n" | ||||
| 
 | ||||
| 	fmt.Print(result) | ||||
| 
 | ||||
| 	return caddy.ExitCodeSuccess, nil | ||||
| } | ||||
							
								
								
									
										485
									
								
								cmd/commands.go
									
									
									
									
									
								
							
							
						
						
									
										485
									
								
								cmd/commands.go
									
									
									
									
									
								
							| @ -15,343 +15,204 @@ | ||||
| package caddycmd | ||||
| 
 | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"crypto/rand" | ||||
| 	"encoding/json" | ||||
| 	"flag" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"io/ioutil" | ||||
| 	"log" | ||||
| 	"net" | ||||
| 	"net/http" | ||||
| 	"os" | ||||
| 	"os/exec" | ||||
| 
 | ||||
| 	"strings" | ||||
| 
 | ||||
| 	"github.com/caddyserver/caddy/v2" | ||||
| 	"github.com/caddyserver/caddy/v2/caddyconfig" | ||||
| 	"github.com/keybase/go-ps" | ||||
| 	"github.com/mholt/certmagic" | ||||
| 	"regexp" | ||||
| ) | ||||
| 
 | ||||
| func cmdStart() (int, error) { | ||||
| 	startCmd := flag.NewFlagSet("start", flag.ExitOnError) | ||||
| 	startCmdConfigFlag := startCmd.String("config", "", "Configuration file") | ||||
| 	startCmdConfigAdapterFlag := startCmd.String("config-adapter", "", "Name of config adapter to apply") | ||||
| 	startCmd.Parse(os.Args[2:]) | ||||
| // Command represents a subcommand. All fields | ||||
| // are required to be set except for Flags if | ||||
| // there are no flags and Usage if there are | ||||
| // no flags or arguments. | ||||
| type Command struct { | ||||
| 	Name string | ||||
| 
 | ||||
| 	// open a listener to which the child process will connect when | ||||
| 	// it is ready to confirm that it has successfully started | ||||
| 	ln, err := net.Listen("tcp", "127.0.0.1:0") | ||||
| 	if err != nil { | ||||
| 		return caddy.ExitCodeFailedStartup, | ||||
| 			fmt.Errorf("opening listener for success confirmation: %v", err) | ||||
| 	} | ||||
| 	defer ln.Close() | ||||
| 	// Run is a function that executes a subcommand. | ||||
| 	// It returns an exit code and any associated error. | ||||
| 	// Takes non-flag commandline arguments as args. | ||||
| 	// Flag must be parsed before Run is executed. | ||||
| 	Func CommandFunc | ||||
| 
 | ||||
| 	// craft the command with a pingback address and with a | ||||
| 	// pipe for its stdin, so we can tell it our confirmation | ||||
| 	// code that we expect so that some random port scan at | ||||
| 	// the most unfortunate time won't fool us into thinking | ||||
| 	// the child succeeded (i.e. the alternative is to just | ||||
| 	// wait for any connection on our listener, but better to | ||||
| 	// ensure it's the process we're expecting - we can be | ||||
| 	// sure by giving it some random bytes and having it echo | ||||
| 	// them back to us) | ||||
| 	cmd := exec.Command(os.Args[0], "run", "--pingback", ln.Addr().String()) | ||||
| 	if *startCmdConfigFlag != "" { | ||||
| 		cmd.Args = append(cmd.Args, "--config", *startCmdConfigFlag) | ||||
| 	} | ||||
| 	if *startCmdConfigAdapterFlag != "" { | ||||
| 		cmd.Args = append(cmd.Args, "--config-adapter", *startCmdConfigAdapterFlag) | ||||
| 	} | ||||
| 	stdinpipe, err := cmd.StdinPipe() | ||||
| 	if err != nil { | ||||
| 		return caddy.ExitCodeFailedStartup, | ||||
| 			fmt.Errorf("creating stdin pipe: %v", err) | ||||
| 	} | ||||
| 	cmd.Stdout = os.Stdout | ||||
| 	cmd.Stderr = os.Stderr | ||||
| 	// Usage is the one-line message explaining args, flags. | ||||
| 	Usage string | ||||
| 
 | ||||
| 	// generate the random bytes we'll send to the child process | ||||
| 	expect := make([]byte, 32) | ||||
| 	_, err = rand.Read(expect) | ||||
| 	if err != nil { | ||||
| 		return caddy.ExitCodeFailedStartup, fmt.Errorf("generating random confirmation bytes: %v", err) | ||||
| 	} | ||||
| 	// Short is the short description for command. | ||||
| 	Short string | ||||
| 
 | ||||
| 	// begin writing the confirmation bytes to the child's | ||||
| 	// stdin; use a goroutine since the child hasn't been | ||||
| 	// started yet, and writing sychronously would result | ||||
| 	// in a deadlock | ||||
| 	go func() { | ||||
| 		stdinpipe.Write(expect) | ||||
| 		stdinpipe.Close() | ||||
| 	}() | ||||
| 	// Long is the message for 'caddy help <command>' | ||||
| 	Long string | ||||
| 
 | ||||
| 	// start the process | ||||
| 	err = cmd.Start() | ||||
| 	if err != nil { | ||||
| 		return caddy.ExitCodeFailedStartup, fmt.Errorf("starting caddy process: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	// there are two ways we know we're done: either | ||||
| 	// the process will connect to our listener, or | ||||
| 	// it will exit with an error | ||||
| 	success, exit := make(chan struct{}), make(chan error) | ||||
| 
 | ||||
| 	// in one goroutine, we await the success of the child process | ||||
| 	go func() { | ||||
| 		for { | ||||
| 			conn, err := ln.Accept() | ||||
| 			if err != nil { | ||||
| 				if !strings.Contains(err.Error(), "use of closed network connection") { | ||||
| 					log.Println(err) | ||||
| 				} | ||||
| 				break | ||||
| 			} | ||||
| 			err = handlePingbackConn(conn, expect) | ||||
| 			if err == nil { | ||||
| 				close(success) | ||||
| 				break | ||||
| 			} | ||||
| 			log.Println(err) | ||||
| 		} | ||||
| 	}() | ||||
| 
 | ||||
| 	// in another goroutine, we await the failure of the child process | ||||
| 	go func() { | ||||
| 		err := cmd.Wait() // don't send on this line! Wait blocks, but send starts before it unblocks | ||||
| 		exit <- err       // sending on separate line ensures select won't trigger until after Wait unblocks | ||||
| 	}() | ||||
| 
 | ||||
| 	// when one of the goroutines unblocks, we're done and can exit | ||||
| 	select { | ||||
| 	case <-success: | ||||
| 		fmt.Printf("Successfully started Caddy (pid=%d)\n", cmd.Process.Pid) | ||||
| 	case err := <-exit: | ||||
| 		return caddy.ExitCodeFailedStartup, | ||||
| 			fmt.Errorf("caddy process exited with error: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	return caddy.ExitCodeSuccess, nil | ||||
| 	// Flags is flagset for command. | ||||
| 	Flags *flag.FlagSet | ||||
| } | ||||
| 
 | ||||
| func cmdRun() (int, error) { | ||||
| 	runCmd := flag.NewFlagSet("run", flag.ExitOnError) | ||||
| 	runCmdConfigFlag := runCmd.String("config", "", "Configuration file") | ||||
| 	runCmdConfigAdapterFlag := runCmd.String("config-adapter", "", "Name of config adapter to apply") | ||||
| 	runCmdPrintEnvFlag := runCmd.Bool("print-env", false, "Print environment") | ||||
| 	runCmdPingbackFlag := runCmd.String("pingback", "", "Echo confirmation bytes to this address on success") | ||||
| 	runCmd.Parse(os.Args[2:]) | ||||
| // CommandFunc is a command's function. It runs the | ||||
| // command and returns the proper exit code along with | ||||
| // any error that occurred. | ||||
| type CommandFunc func(Flags) (int, error) | ||||
| 
 | ||||
| 	// if we are supposed to print the environment, do that first | ||||
| 	if *runCmdPrintEnvFlag { | ||||
| 		exitCode, err := cmdEnviron() | ||||
| 		if err != nil { | ||||
| 			return exitCode, err | ||||
| 		} | ||||
| 	} | ||||
| var commands = map[string]Command{ | ||||
| 	"start": { | ||||
| 		Name:  "start", | ||||
| 		Func:  cmdStart, | ||||
| 		Usage: "[--config <path>] [--config-adapter <name>]", | ||||
| 		Short: "Starts the Caddy process and returns after server has started.", | ||||
| 		Long: ` | ||||
| Starts the Caddy process, optionally bootstrapped with an initial | ||||
| config file. Blocks until server is successfully running (or fails to run), | ||||
| then returns. On Windows, the child process will remain attached to the | ||||
| terminal, so closing the window will forcefully stop Caddy. See run for more | ||||
| details.`, | ||||
| 		Flags: func() *flag.FlagSet { | ||||
| 			fs := flag.NewFlagSet("start", flag.ExitOnError) | ||||
| 			fs.String("config", "", "Configuration file") | ||||
| 			fs.String("config-adapter", "", "Name of config adapter to apply") | ||||
| 			return fs | ||||
| 		}(), | ||||
| 	}, | ||||
| 
 | ||||
| 	// get the config in caddy's native format | ||||
| 	config, err := loadConfig(*runCmdConfigFlag, *runCmdConfigAdapterFlag) | ||||
| 	if err != nil { | ||||
| 		return caddy.ExitCodeFailedStartup, err | ||||
| 	} | ||||
| 	"run": { | ||||
| 		Name:  "run", | ||||
| 		Func:  cmdRun, | ||||
| 		Usage: "[--config <path>] [--config-adapter <name>] [--print-env]", | ||||
| 		Short: `Starts the Caddy process and blocks indefinitely.`, | ||||
| 		Long: ` | ||||
| Same as start, but blocks indefinitely; i.e. runs Caddy in "daemon" mode. On | ||||
| Windows, this is recommended over caddy start when running Caddy manually since | ||||
| it will be more obvious that Caddy is still running and bound to the terminal | ||||
| window. | ||||
| 
 | ||||
| 	// set a fitting User-Agent for ACME requests | ||||
| 	goModule := caddy.GoModule() | ||||
| 	cleanModVersion := strings.TrimPrefix(goModule.Version, "v") | ||||
| 	certmagic.UserAgent = "Caddy/" + cleanModVersion | ||||
| If a config file is specified, it will be applied immediately after the process | ||||
| is running. If the config file is not in Caddy's native JSON format, you can | ||||
| specify an adapter with --config-adapter to adapt the given config file to | ||||
| Caddy's native format. The config adapter must be a registered module. Any | ||||
| warnings will be printed to the log, but beware that any adaptation without | ||||
| errors will immediately be used. If you want to review the results of the | ||||
| adaptation first, use adapt-config. | ||||
| 
 | ||||
| 	// start the admin endpoint along with any initial config | ||||
| 	err = caddy.StartAdmin(config) | ||||
| 	if err != nil { | ||||
| 		return caddy.ExitCodeFailedStartup, | ||||
| 			fmt.Errorf("starting caddy administration endpoint: %v", err) | ||||
| 	} | ||||
| 	defer caddy.StopAdmin() | ||||
| As a special case, if the current working directory has a file called | ||||
| "Caddyfile" and the caddyfile config adapter is plugged in (default), then that | ||||
| file will be loaded and used to configure Caddy, even without any command line | ||||
| flags. | ||||
| 
 | ||||
| 	// if we are to report to another process the successful start | ||||
| 	// of the server, do so now by echoing back contents of stdin | ||||
| 	if *runCmdPingbackFlag != "" { | ||||
| 		confirmationBytes, err := ioutil.ReadAll(os.Stdin) | ||||
| 		if err != nil { | ||||
| 			return caddy.ExitCodeFailedStartup, | ||||
| 				fmt.Errorf("reading confirmation bytes from stdin: %v", err) | ||||
| 		} | ||||
| 		conn, err := net.Dial("tcp", *runCmdPingbackFlag) | ||||
| 		if err != nil { | ||||
| 			return caddy.ExitCodeFailedStartup, | ||||
| 				fmt.Errorf("dialing confirmation address: %v", err) | ||||
| 		} | ||||
| 		defer conn.Close() | ||||
| 		_, err = conn.Write(confirmationBytes) | ||||
| 		if err != nil { | ||||
| 			return caddy.ExitCodeFailedStartup, | ||||
| 				fmt.Errorf("writing confirmation bytes to %s: %v", *runCmdPingbackFlag, err) | ||||
| 		} | ||||
| 	} | ||||
| If --print-env is specified, the environment as seen by the Caddy process will | ||||
| be printed before starting. This is the same as the environ command but does | ||||
| not quit after printing.`, | ||||
| 		Flags: func() *flag.FlagSet { | ||||
| 			fs := flag.NewFlagSet("run", flag.ExitOnError) | ||||
| 			fs.String("config", "", "Configuration file") | ||||
| 			fs.String("config-adapter", "", "Name of config adapter to apply") | ||||
| 			fs.Bool("print-env", false, "Print environment") | ||||
| 			fs.String("pingback", "", "Echo confirmation bytes to this address on success") | ||||
| 			return fs | ||||
| 		}(), | ||||
| 	}, | ||||
| 
 | ||||
| 	select {} | ||||
| 	"stop": { | ||||
| 		Name:  "stop", | ||||
| 		Func:  cmdStop, | ||||
| 		Short: "Gracefully stops the running Caddy process", | ||||
| 		Long: `Gracefully stops the running Caddy process. (Note: this will stop any process | ||||
| named the same as the executable.) On Windows, this stop is forceful and Caddy | ||||
| will not have an opportunity to clean up any active locks; for a graceful | ||||
| shutdown on Windows, use Ctrl+C or the /stop endpoint.`, | ||||
| 	}, | ||||
| 
 | ||||
| 	"reload": { | ||||
| 		Name:  "reload", | ||||
| 		Func:  cmdReload, | ||||
| 		Usage: "--config <path> [--config-adapter <name>] [--address <interface>]", | ||||
| 		Short: "Gives the running Caddy instance a new configuration", | ||||
| 		Long: `Gives the running Caddy instance a new configuration. This has the same effect | ||||
| as POSTing a document to the /load endpoint, but is convenient for simple | ||||
| workflows revolving around config files. Since the admin endpoint is | ||||
| configurable, the endpoint configuration is loaded from the --address flag if | ||||
| specified; otherwise it is loaded from the given config file; otherwise the | ||||
| default is assumed.`, | ||||
| 		Flags: func() *flag.FlagSet { | ||||
| 			fs := flag.NewFlagSet("load", flag.ExitOnError) | ||||
| 			fs.String("config", "", "Configuration file") | ||||
| 			fs.String("config-adapter", "", "Name of config adapter to apply") | ||||
| 			fs.String("address", "", "Address of the administration listener, if different from config") | ||||
| 			return fs | ||||
| 		}(), | ||||
| 	}, | ||||
| 
 | ||||
| 	"version": { | ||||
| 		Name:  "version", | ||||
| 		Func:  cmdVersion, | ||||
| 		Short: "Prints the version.", | ||||
| 		Long:  `Prints the version.`, | ||||
| 	}, | ||||
| 
 | ||||
| 	"list-modules": { | ||||
| 		Name:  "list-modules", | ||||
| 		Func:  cmdListModules, | ||||
| 		Short: "List installed Caddy modules.", | ||||
| 		Long:  `List installed Caddy modules.`, | ||||
| 	}, | ||||
| 
 | ||||
| 	"environ": { | ||||
| 		Name:  "environ", | ||||
| 		Func:  cmdEnviron, | ||||
| 		Short: "Prints the environment as seen by Caddy.", | ||||
| 		Long:  `Prints the environment as seen by Caddy.`, | ||||
| 	}, | ||||
| 
 | ||||
| 	"adapt-config": { | ||||
| 		Name:  "adapt-config", | ||||
| 		Func:  cmdAdaptConfig, | ||||
| 		Usage: "--input <path> --adapter <name> [--pretty]", | ||||
| 		Short: "Adapts a configuration to Caddy's native JSON config structure", | ||||
| 		Long: ` | ||||
| Adapts a configuration to Caddy's native JSON config structure and writes the | ||||
| output to stdout, along with any warnings to stderr. If --pretty is specified, | ||||
| the output will be formatted with indentation for human readability.`, | ||||
| 	}, | ||||
| } | ||||
| 
 | ||||
| func cmdStop() (int, error) { | ||||
| 	processList, err := ps.Processes() | ||||
| 	if err != nil { | ||||
| 		return caddy.ExitCodeFailedStartup, fmt.Errorf("listing processes: %v", err) | ||||
| func init() { | ||||
| 	// the help command is special in that its func | ||||
| 	// refers to the commands map; thus, defining it | ||||
| 	// inline with the commands map's initialization | ||||
| 	// yields a compile-time error, so we have to | ||||
| 	// define this command separately | ||||
| 	commands["help"] = Command{ | ||||
| 		Name:  "help", | ||||
| 		Func:  cmdHelp, | ||||
| 		Usage: "<command>", | ||||
| 		Short: "Shows help for a Caddy subcommand.", | ||||
| 	} | ||||
| 	thisProcName := getProcessName() | ||||
| 	var found bool | ||||
| 	for _, p := range processList { | ||||
| 		// the process we're looking for should have the same name but different PID | ||||
| 		if p.Executable() == thisProcName && p.Pid() != os.Getpid() { | ||||
| 			found = true | ||||
| 			fmt.Printf("pid=%d\n", p.Pid()) | ||||
| 
 | ||||
| 			if err := gracefullyStopProcess(p.Pid()); err != nil { | ||||
| 				return caddy.ExitCodeFailedStartup, err | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	if !found { | ||||
| 		return caddy.ExitCodeFailedStartup, fmt.Errorf("Caddy is not running") | ||||
| 	} | ||||
| 	fmt.Println(" success") | ||||
| 	return caddy.ExitCodeSuccess, nil | ||||
| } | ||||
| 
 | ||||
| func cmdReload() (int, error) { | ||||
| 	reloadCmd := flag.NewFlagSet("load", flag.ExitOnError) | ||||
| 	reloadCmdConfigFlag := reloadCmd.String("config", "", "Configuration file") | ||||
| 	reloadCmdConfigAdapterFlag := reloadCmd.String("config-adapter", "", "Name of config adapter to apply") | ||||
| 	reloadCmdAddrFlag := reloadCmd.String("address", "", "Address of the administration listener, if different from config") | ||||
| 	reloadCmd.Parse(os.Args[2:]) | ||||
| 
 | ||||
| 	// a configuration is required | ||||
| 	if *reloadCmdConfigFlag == "" { | ||||
| 		return caddy.ExitCodeFailedStartup, | ||||
| 			fmt.Errorf("no configuration to load (use --config)") | ||||
| // RegisterCommand registers the command cmd. | ||||
| // cmd.Name must be unique and conform to the | ||||
| // following format: | ||||
| // | ||||
| //    - lowercase | ||||
| //    - alphanumeric and hyphen characters only | ||||
| //    - cannot start or end with a hyphen | ||||
| //    - hyphen cannot be adjacent to another hyphen | ||||
| // | ||||
| // This function panics if the name is already registered, | ||||
| // if the name does not meet the described format, or if | ||||
| // any of the fields are missing from cmd. | ||||
| func RegisterCommand(cmd Command) { | ||||
| 	if cmd.Name == "" { | ||||
| 		panic("command name is required") | ||||
| 	} | ||||
| 
 | ||||
| 	// get the config in caddy's native format | ||||
| 	config, err := loadConfig(*reloadCmdConfigFlag, *reloadCmdConfigAdapterFlag) | ||||
| 	if err != nil { | ||||
| 		return caddy.ExitCodeFailedStartup, err | ||||
| 	if cmd.Func == nil { | ||||
| 		panic("command function missing") | ||||
| 	} | ||||
| 
 | ||||
| 	// get the address of the admin listener and craft endpoint URL | ||||
| 	adminAddr := *reloadCmdAddrFlag | ||||
| 	if adminAddr == "" { | ||||
| 		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 | ||||
| 	if cmd.Short == "" { | ||||
| 		panic("command short string is required") | ||||
| 	} | ||||
| 	if adminAddr == "" { | ||||
| 		adminAddr = caddy.DefaultAdminListen | ||||
| 	if _, exists := commands[cmd.Name]; exists { | ||||
| 		panic("command already registered: " + cmd.Name) | ||||
| 	} | ||||
| 	adminEndpoint := fmt.Sprintf("http://%s/load", adminAddr) | ||||
| 
 | ||||
| 	// send the configuration to the instance | ||||
| 	resp, err := http.Post(adminEndpoint, "application/json", bytes.NewReader(config)) | ||||
| 	if err != nil { | ||||
| 		return caddy.ExitCodeFailedStartup, | ||||
| 			fmt.Errorf("sending configuration to instance: %v", err) | ||||
| 	if !commandNameRegex.MatchString(cmd.Name) { | ||||
| 		panic("invalid command name") | ||||
| 	} | ||||
| 	defer resp.Body.Close() | ||||
| 
 | ||||
| 	// if it didn't work, let the user know | ||||
| 	if resp.StatusCode >= 400 { | ||||
| 		respBody, err := ioutil.ReadAll(io.LimitReader(resp.Body, 1024*10)) | ||||
| 		if err != nil { | ||||
| 			return caddy.ExitCodeFailedStartup, | ||||
| 				fmt.Errorf("HTTP %d: reading error message: %v", resp.StatusCode, err) | ||||
| 		} | ||||
| 		return caddy.ExitCodeFailedStartup, | ||||
| 			fmt.Errorf("caddy responded with error: HTTP %d: %s", resp.StatusCode, respBody) | ||||
| 	} | ||||
| 
 | ||||
| 	return caddy.ExitCodeSuccess, nil | ||||
| 	commands[cmd.Name] = cmd | ||||
| } | ||||
| 
 | ||||
| func cmdVersion() (int, error) { | ||||
| 	goModule := caddy.GoModule() | ||||
| 	if goModule.Sum != "" { | ||||
| 		// a build with a known version will also have a checksum | ||||
| 		fmt.Printf("%s %s\n", goModule.Version, goModule.Sum) | ||||
| 	} else { | ||||
| 		fmt.Println(goModule.Version) | ||||
| 	} | ||||
| 	return caddy.ExitCodeSuccess, nil | ||||
| } | ||||
| 
 | ||||
| func cmdListModules() (int, error) { | ||||
| 	for _, m := range caddy.Modules() { | ||||
| 		fmt.Println(m) | ||||
| 	} | ||||
| 	return caddy.ExitCodeSuccess, nil | ||||
| } | ||||
| 
 | ||||
| func cmdEnviron() (int, error) { | ||||
| 	for _, v := range os.Environ() { | ||||
| 		fmt.Println(v) | ||||
| 	} | ||||
| 	return caddy.ExitCodeSuccess, nil | ||||
| } | ||||
| 
 | ||||
| func cmdAdaptConfig() (int, error) { | ||||
| 	adaptCmd := flag.NewFlagSet("adapt", flag.ExitOnError) | ||||
| 	adaptCmdAdapterFlag := adaptCmd.String("adapter", "", "Name of config adapter") | ||||
| 	adaptCmdInputFlag := adaptCmd.String("input", "", "Configuration file to adapt") | ||||
| 	adaptCmdPrettyFlag := adaptCmd.Bool("pretty", false, "Format the output for human readability") | ||||
| 	adaptCmd.Parse(os.Args[2:]) | ||||
| 
 | ||||
| 	if *adaptCmdAdapterFlag == "" || *adaptCmdInputFlag == "" { | ||||
| 		return caddy.ExitCodeFailedStartup, | ||||
| 			fmt.Errorf("usage: caddy adapt-config --adapter <name> --input <file>") | ||||
| 	} | ||||
| 
 | ||||
| 	cfgAdapter := caddyconfig.GetAdapter(*adaptCmdAdapterFlag) | ||||
| 	if cfgAdapter == nil { | ||||
| 		return caddy.ExitCodeFailedStartup, | ||||
| 			fmt.Errorf("unrecognized config adapter: %s", *adaptCmdAdapterFlag) | ||||
| 	} | ||||
| 
 | ||||
| 	input, err := ioutil.ReadFile(*adaptCmdInputFlag) | ||||
| 	if err != nil { | ||||
| 		return caddy.ExitCodeFailedStartup, | ||||
| 			fmt.Errorf("reading input file: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	opts := make(map[string]interface{}) | ||||
| 	if *adaptCmdPrettyFlag { | ||||
| 		opts["pretty"] = "true" | ||||
| 	} | ||||
| 
 | ||||
| 	adaptedConfig, warnings, err := cfgAdapter.Adapt(input, opts) | ||||
| 	if err != nil { | ||||
| 		return caddy.ExitCodeFailedStartup, err | ||||
| 	} | ||||
| 
 | ||||
| 	// print warnings to stderr | ||||
| 	for _, warn := range warnings { | ||||
| 		msg := warn.Message | ||||
| 		if warn.Directive != "" { | ||||
| 			msg = fmt.Sprintf("%s: %s", warn.Directive, warn.Message) | ||||
| 		} | ||||
| 		log.Printf("[WARNING][%s] %s:%d: %s", *adaptCmdAdapterFlag, warn.File, warn.Line, msg) | ||||
| 	} | ||||
| 
 | ||||
| 	// print result to stdout | ||||
| 	fmt.Println(string(adaptedConfig)) | ||||
| 
 | ||||
| 	return caddy.ExitCodeSuccess, nil | ||||
| } | ||||
| var commandNameRegex = regexp.MustCompile(`^[a-z0-9]$|^([a-z0-9]+-?[a-z0-9]*)+[a-z0-9]$`) | ||||
|  | ||||
							
								
								
									
										130
									
								
								cmd/main.go
									
									
									
									
									
								
							
							
						
						
									
										130
									
								
								cmd/main.go
									
									
									
									
									
								
							| @ -23,6 +23,9 @@ import ( | ||||
| 	"log" | ||||
| 	"net" | ||||
| 	"os" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| 	"time" | ||||
| 
 | ||||
| 	"github.com/caddyserver/caddy/v2" | ||||
| 	"github.com/caddyserver/caddy/v2/caddyconfig" | ||||
| @ -33,45 +36,43 @@ import ( | ||||
| func Main() { | ||||
| 	caddy.TrapSignals() | ||||
| 
 | ||||
| 	if len(os.Args) < 2 { | ||||
| 		fmt.Println(usageString()) | ||||
| 		return | ||||
| 	switch len(os.Args) { | ||||
| 	case 0: | ||||
| 		log.Printf("[FATAL] no arguments provided by OS; args[0] must be command") | ||||
| 		os.Exit(caddy.ExitCodeFailedStartup) | ||||
| 	case 1: | ||||
| 		os.Args = append(os.Args, "help") | ||||
| 	} | ||||
| 
 | ||||
| 	subcommand, ok := commands[os.Args[1]] | ||||
| 	subcommandName := os.Args[1] | ||||
| 	subcommand, ok := commands[subcommandName] | ||||
| 	if !ok { | ||||
| 		fmt.Printf("%q is not a valid command\n", os.Args[1]) | ||||
| 		if strings.HasPrefix(os.Args[1], "-") { | ||||
| 			// user probably forgot to type the subcommand | ||||
| 			log.Println("[ERROR] first argument must be a subcommand; see 'caddy help'") | ||||
| 		} else { | ||||
| 			log.Printf("[ERROR] '%s' is not a recognized subcommand; see 'caddy help'", os.Args[1]) | ||||
| 		} | ||||
| 		os.Exit(caddy.ExitCodeFailedStartup) | ||||
| 	} | ||||
| 
 | ||||
| 	if exitCode, err := subcommand(); err != nil { | ||||
| 		log.Println(err) | ||||
| 		os.Exit(exitCode) | ||||
| 	fs := subcommand.Flags | ||||
| 	if fs == nil { | ||||
| 		fs = flag.NewFlagSet(subcommand.Name, flag.ExitOnError) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // commandFunc is a function that executes | ||||
| // a subcommand. It returns an exit code and | ||||
| // any associated error. | ||||
| type commandFunc func() (int, error) | ||||
| 	err := fs.Parse(os.Args[2:]) | ||||
| 	if err != nil { | ||||
| 		log.Println(err) | ||||
| 		os.Exit(caddy.ExitCodeFailedStartup) | ||||
| 	} | ||||
| 
 | ||||
| var commands = map[string]commandFunc{ | ||||
| 	"start":        cmdStart, | ||||
| 	"run":          cmdRun, | ||||
| 	"stop":         cmdStop, | ||||
| 	"reload":       cmdReload, | ||||
| 	"version":      cmdVersion, | ||||
| 	"list-modules": cmdListModules, | ||||
| 	"environ":      cmdEnviron, | ||||
| 	"adapt-config": cmdAdaptConfig, | ||||
| } | ||||
| 	exitCode, err := subcommand.Func(Flags{fs}) | ||||
| 	if err != nil { | ||||
| 		log.Printf("%s: %v", subcommand.Name, err) | ||||
| 	} | ||||
| 
 | ||||
| func usageString() string { | ||||
| 	buf := new(bytes.Buffer) | ||||
| 	buf.WriteString("usage: caddy <command> [<args>]") | ||||
| 	flag.CommandLine.SetOutput(buf) | ||||
| 	flag.CommandLine.PrintDefaults() | ||||
| 	return buf.String() | ||||
| 	os.Exit(exitCode) | ||||
| } | ||||
| 
 | ||||
| // handlePingbackConn reads from conn and ensures it matches | ||||
| @ -156,3 +157,74 @@ func loadConfig(configFile, adapterName string) ([]byte, error) { | ||||
| 
 | ||||
| 	return config, nil | ||||
| } | ||||
| 
 | ||||
| // Flags wraps a FlagSet so that typed values | ||||
| // from flags can be easily retrieved. | ||||
| type Flags struct { | ||||
| 	*flag.FlagSet | ||||
| } | ||||
| 
 | ||||
| // String returns the string representation of the | ||||
| // flag given by name. It panics if the flag is not | ||||
| // in the flag set. | ||||
| func (f Flags) String(name string) string { | ||||
| 	return f.FlagSet.Lookup(name).Value.String() | ||||
| } | ||||
| 
 | ||||
| // Bool returns the boolean representation of the | ||||
| // flag given by name. It returns false if the flag | ||||
| // is not a boolean type. It panics if the flag is | ||||
| // not in the flag set. | ||||
| func (f Flags) Bool(name string) bool { | ||||
| 	val, _ := strconv.ParseBool(f.String(name)) | ||||
| 	return val | ||||
| } | ||||
| 
 | ||||
| // Int returns the integer representation of the | ||||
| // flag given by name. It returns 0 if the flag | ||||
| // is not an integer type. It panics if the flag is | ||||
| // not in the flag set. | ||||
| func (f Flags) Int(name string) int { | ||||
| 	val, _ := strconv.ParseInt(f.String(name), 0, strconv.IntSize) | ||||
| 	return int(val) | ||||
| } | ||||
| 
 | ||||
| // Float64 returns the float64 representation of the | ||||
| // flag given by name. It returns false if the flag | ||||
| // is not a float63 type. It panics if the flag is | ||||
| // not in the flag set. | ||||
| func (f Flags) Float64(name string) float64 { | ||||
| 	val, _ := strconv.ParseFloat(f.String(name), 64) | ||||
| 	return val | ||||
| } | ||||
| 
 | ||||
| // Duration returns the duration representation of the | ||||
| // flag given by name. It returns false if the flag | ||||
| // is not a duration type. It panics if the flag is | ||||
| // not in the flag set. | ||||
| func (f Flags) Duration(name string) time.Duration { | ||||
| 	val, _ := time.ParseDuration(f.String(name)) | ||||
| 	return val | ||||
| } | ||||
| 
 | ||||
| // flagHelp returns the help text for fs. | ||||
| func flagHelp(fs *flag.FlagSet) string { | ||||
| 	if fs == nil { | ||||
| 		return "" | ||||
| 	} | ||||
| 
 | ||||
| 	// temporarily redirect output | ||||
| 	out := fs.Output() | ||||
| 	defer fs.SetOutput(out) | ||||
| 
 | ||||
| 	buf := new(bytes.Buffer) | ||||
| 	fs.SetOutput(buf) | ||||
| 	fs.PrintDefaults() | ||||
| 	return buf.String() | ||||
| } | ||||
| 
 | ||||
| func printEnvironment() { | ||||
| 	for _, v := range os.Environ() { | ||||
| 		fmt.Println(v) | ||||
| 	} | ||||
| } | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user