mirror of
https://github.com/caddyserver/caddy.git
synced 2024-12-16 21:56:40 -05: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
3 changed files with 665 additions and 341 deletions
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
|
package caddycmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"crypto/rand"
|
|
||||||
"encoding/json"
|
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"regexp"
|
||||||
"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() (int, error) {
|
// Command represents a subcommand. All fields
|
||||||
startCmd := flag.NewFlagSet("start", flag.ExitOnError)
|
// are required to be set except for Flags if
|
||||||
startCmdConfigFlag := startCmd.String("config", "", "Configuration file")
|
// there are no flags and Usage if there are
|
||||||
startCmdConfigAdapterFlag := startCmd.String("config-adapter", "", "Name of config adapter to apply")
|
// no flags or arguments.
|
||||||
startCmd.Parse(os.Args[2:])
|
type Command struct {
|
||||||
|
Name string
|
||||||
|
|
||||||
// open a listener to which the child process will connect when
|
// Run is a function that executes a subcommand.
|
||||||
// it is ready to confirm that it has successfully started
|
// It returns an exit code and any associated error.
|
||||||
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
// Takes non-flag commandline arguments as args.
|
||||||
if err != nil {
|
// Flag must be parsed before Run is executed.
|
||||||
return caddy.ExitCodeFailedStartup,
|
Func CommandFunc
|
||||||
fmt.Errorf("opening listener for success confirmation: %v", err)
|
|
||||||
}
|
|
||||||
defer ln.Close()
|
|
||||||
|
|
||||||
// craft the command with a pingback address and with a
|
// Usage is the one-line message explaining args, flags.
|
||||||
// pipe for its stdin, so we can tell it our confirmation
|
Usage string
|
||||||
// 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
|
// Short is the short description for command.
|
||||||
expect := make([]byte, 32)
|
Short string
|
||||||
_, 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
|
// Long is the message for 'caddy help <command>'
|
||||||
// stdin; use a goroutine since the child hasn't been
|
Long string
|
||||||
// started yet, and writing sychronously would result
|
|
||||||
// in a deadlock
|
|
||||||
go func() {
|
|
||||||
stdinpipe.Write(expect)
|
|
||||||
stdinpipe.Close()
|
|
||||||
}()
|
|
||||||
|
|
||||||
// start the process
|
// Flags is flagset for command.
|
||||||
err = cmd.Start()
|
Flags *flag.FlagSet
|
||||||
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() (int, error) {
|
// CommandFunc is a command's function. It runs the
|
||||||
runCmd := flag.NewFlagSet("run", flag.ExitOnError)
|
// command and returns the proper exit code along with
|
||||||
runCmdConfigFlag := runCmd.String("config", "", "Configuration file")
|
// any error that occurred.
|
||||||
runCmdConfigAdapterFlag := runCmd.String("config-adapter", "", "Name of config adapter to apply")
|
type CommandFunc func(Flags) (int, error)
|
||||||
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:])
|
|
||||||
|
|
||||||
// if we are supposed to print the environment, do that first
|
var commands = map[string]Command{
|
||||||
if *runCmdPrintEnvFlag {
|
"start": {
|
||||||
exitCode, err := cmdEnviron()
|
Name: "start",
|
||||||
if err != nil {
|
Func: cmdStart,
|
||||||
return exitCode, err
|
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
|
"run": {
|
||||||
config, err := loadConfig(*runCmdConfigFlag, *runCmdConfigAdapterFlag)
|
Name: "run",
|
||||||
if err != nil {
|
Func: cmdRun,
|
||||||
return caddy.ExitCodeFailedStartup, err
|
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
|
If a config file is specified, it will be applied immediately after the process
|
||||||
goModule := caddy.GoModule()
|
is running. If the config file is not in Caddy's native JSON format, you can
|
||||||
cleanModVersion := strings.TrimPrefix(goModule.Version, "v")
|
specify an adapter with --config-adapter to adapt the given config file to
|
||||||
certmagic.UserAgent = "Caddy/" + cleanModVersion
|
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
|
As a special case, if the current working directory has a file called
|
||||||
err = caddy.StartAdmin(config)
|
"Caddyfile" and the caddyfile config adapter is plugged in (default), then that
|
||||||
if err != nil {
|
file will be loaded and used to configure Caddy, even without any command line
|
||||||
return caddy.ExitCodeFailedStartup,
|
flags.
|
||||||
fmt.Errorf("starting caddy administration endpoint: %v", err)
|
|
||||||
}
|
|
||||||
defer caddy.StopAdmin()
|
|
||||||
|
|
||||||
// if we are to report to another process the successful start
|
If --print-env is specified, the environment as seen by the Caddy process will
|
||||||
// of the server, do so now by echoing back contents of stdin
|
be printed before starting. This is the same as the environ command but does
|
||||||
if *runCmdPingbackFlag != "" {
|
not quit after printing.`,
|
||||||
confirmationBytes, err := ioutil.ReadAll(os.Stdin)
|
Flags: func() *flag.FlagSet {
|
||||||
if err != nil {
|
fs := flag.NewFlagSet("run", flag.ExitOnError)
|
||||||
return caddy.ExitCodeFailedStartup,
|
fs.String("config", "", "Configuration file")
|
||||||
fmt.Errorf("reading confirmation bytes from stdin: %v", err)
|
fs.String("config-adapter", "", "Name of config adapter to apply")
|
||||||
}
|
fs.Bool("print-env", false, "Print environment")
|
||||||
conn, err := net.Dial("tcp", *runCmdPingbackFlag)
|
fs.String("pingback", "", "Echo confirmation bytes to this address on success")
|
||||||
if err != nil {
|
return fs
|
||||||
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 {}
|
"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) {
|
func init() {
|
||||||
processList, err := ps.Processes()
|
// the help command is special in that its func
|
||||||
if err != nil {
|
// refers to the commands map; thus, defining it
|
||||||
return caddy.ExitCodeFailedStartup, fmt.Errorf("listing processes: %v", err)
|
// 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) {
|
// RegisterCommand registers the command cmd.
|
||||||
reloadCmd := flag.NewFlagSet("load", flag.ExitOnError)
|
// cmd.Name must be unique and conform to the
|
||||||
reloadCmdConfigFlag := reloadCmd.String("config", "", "Configuration file")
|
// following format:
|
||||||
reloadCmdConfigAdapterFlag := reloadCmd.String("config-adapter", "", "Name of config adapter to apply")
|
//
|
||||||
reloadCmdAddrFlag := reloadCmd.String("address", "", "Address of the administration listener, if different from config")
|
// - lowercase
|
||||||
reloadCmd.Parse(os.Args[2:])
|
// - alphanumeric and hyphen characters only
|
||||||
|
// - cannot start or end with a hyphen
|
||||||
// a configuration is required
|
// - hyphen cannot be adjacent to another hyphen
|
||||||
if *reloadCmdConfigFlag == "" {
|
//
|
||||||
return caddy.ExitCodeFailedStartup,
|
// This function panics if the name is already registered,
|
||||||
fmt.Errorf("no configuration to load (use --config)")
|
// 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")
|
||||||
}
|
}
|
||||||
|
if cmd.Func == nil {
|
||||||
// get the config in caddy's native format
|
panic("command function missing")
|
||||||
config, err := loadConfig(*reloadCmdConfigFlag, *reloadCmdConfigAdapterFlag)
|
|
||||||
if err != nil {
|
|
||||||
return caddy.ExitCodeFailedStartup, err
|
|
||||||
}
|
}
|
||||||
|
if cmd.Short == "" {
|
||||||
// get the address of the admin listener and craft endpoint URL
|
panic("command short string is required")
|
||||||
adminAddr := *reloadCmdAddrFlag
|
|
||||||
if adminAddr == "" {
|
|
||||||
var tmpStruct struct {
|
|
||||||
Admin caddy.AdminConfig `json:"admin"`
|
|
||||||
}
|
}
|
||||||
err = json.Unmarshal(config, &tmpStruct)
|
if _, exists := commands[cmd.Name]; exists {
|
||||||
if err != nil {
|
panic("command already registered: " + cmd.Name)
|
||||||
return caddy.ExitCodeFailedStartup,
|
|
||||||
fmt.Errorf("unmarshaling admin listener address from config: %v", err)
|
|
||||||
}
|
}
|
||||||
adminAddr = tmpStruct.Admin.Listen
|
if !commandNameRegex.MatchString(cmd.Name) {
|
||||||
|
panic("invalid command name")
|
||||||
}
|
}
|
||||||
if adminAddr == "" {
|
commands[cmd.Name] = cmd
|
||||||
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() (int, error) {
|
var commandNameRegex = regexp.MustCompile(`^[a-z0-9]$|^([a-z0-9]+-?[a-z0-9]*)+[a-z0-9]$`)
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
130
cmd/main.go
130
cmd/main.go
|
@ -23,6 +23,9 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/caddyserver/caddy/v2"
|
"github.com/caddyserver/caddy/v2"
|
||||||
"github.com/caddyserver/caddy/v2/caddyconfig"
|
"github.com/caddyserver/caddy/v2/caddyconfig"
|
||||||
|
@ -33,45 +36,43 @@ import (
|
||||||
func Main() {
|
func Main() {
|
||||||
caddy.TrapSignals()
|
caddy.TrapSignals()
|
||||||
|
|
||||||
if len(os.Args) < 2 {
|
switch len(os.Args) {
|
||||||
fmt.Println(usageString())
|
case 0:
|
||||||
return
|
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 {
|
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)
|
os.Exit(caddy.ExitCodeFailedStartup)
|
||||||
}
|
}
|
||||||
|
|
||||||
if exitCode, err := subcommand(); err != nil {
|
fs := subcommand.Flags
|
||||||
log.Println(err)
|
if fs == nil {
|
||||||
os.Exit(exitCode)
|
fs = flag.NewFlagSet(subcommand.Name, flag.ExitOnError)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// commandFunc is a function that executes
|
err := fs.Parse(os.Args[2:])
|
||||||
// a subcommand. It returns an exit code and
|
if err != nil {
|
||||||
// any associated error.
|
log.Println(err)
|
||||||
type commandFunc func() (int, error)
|
os.Exit(caddy.ExitCodeFailedStartup)
|
||||||
|
}
|
||||||
|
|
||||||
var commands = map[string]commandFunc{
|
exitCode, err := subcommand.Func(Flags{fs})
|
||||||
"start": cmdStart,
|
if err != nil {
|
||||||
"run": cmdRun,
|
log.Printf("%s: %v", subcommand.Name, err)
|
||||||
"stop": cmdStop,
|
}
|
||||||
"reload": cmdReload,
|
|
||||||
"version": cmdVersion,
|
|
||||||
"list-modules": cmdListModules,
|
|
||||||
"environ": cmdEnviron,
|
|
||||||
"adapt-config": cmdAdaptConfig,
|
|
||||||
}
|
|
||||||
|
|
||||||
func usageString() string {
|
os.Exit(exitCode)
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
buf.WriteString("usage: caddy <command> [<args>]")
|
|
||||||
flag.CommandLine.SetOutput(buf)
|
|
||||||
flag.CommandLine.PrintDefaults()
|
|
||||||
return buf.String()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// handlePingbackConn reads from conn and ensures it matches
|
// handlePingbackConn reads from conn and ensures it matches
|
||||||
|
@ -156,3 +157,74 @@ func loadConfig(configFile, adapterName string) ([]byte, error) {
|
||||||
|
|
||||||
return config, nil
|
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…
Reference in a new issue