From ab885f07b844fd60adb9d49ed7884f3cd2d939a7 Mon Sep 17 00:00:00 2001 From: Matthew Holt Date: Fri, 9 Aug 2019 12:05:47 -0600 Subject: Implement config adapters and beginning of Caddyfile adapter Along with several other changes, such as renaming caddyhttp.ServerRoute to caddyhttp.Route, exporting some types that were not exported before, and tweaking the caddytls TLS values to be more consistent. Notably, we also now disable automatic cert management for names which already have a cert (manually) loaded into the cache. These names no longer need to be specified in the "skip_certificates" field of the automatic HTTPS config, because they will be skipped automatically. --- cmd/caddy/main.go | 1 + cmd/commands.go | 81 ++++++++++++++++++++++++++++++++++++++++++++----------- cmd/main.go | 65 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 131 insertions(+), 16 deletions(-) (limited to 'cmd') diff --git a/cmd/caddy/main.go b/cmd/caddy/main.go index c1824d7..5f6b8bb 100644 --- a/cmd/caddy/main.go +++ b/cmd/caddy/main.go @@ -18,6 +18,7 @@ import ( caddycmd "github.com/caddyserver/caddy/v2/cmd" // this is where modules get plugged in + _ "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" _ "github.com/caddyserver/caddy/v2/modules/caddyhttp" _ "github.com/caddyserver/caddy/v2/modules/caddyhttp/encode" _ "github.com/caddyserver/caddy/v2/modules/caddyhttp/encode/brotli" diff --git a/cmd/commands.go b/cmd/commands.go index e63e2d5..99ec642 100644 --- a/cmd/commands.go +++ b/cmd/commands.go @@ -31,6 +31,7 @@ import ( "strings" "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig" "github.com/mholt/certmagic" "github.com/mitchellh/go-ps" ) @@ -38,6 +39,7 @@ import ( 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:]) // open a listener to which the child process will connect when @@ -62,6 +64,9 @@ func cmdStart() (int, error) { 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, @@ -137,7 +142,8 @@ func cmdStart() (int, error) { func cmdRun() (int, error) { runCmd := flag.NewFlagSet("run", flag.ExitOnError) runCmdConfigFlag := runCmd.String("config", "", "Configuration file") - runCmdPrintEnvFlag := runCmd.Bool("print-env", false, "Print environment (useful for debugging)") + 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:]) @@ -149,16 +155,10 @@ func cmdRun() (int, error) { } } - // if a config file was specified for bootstrapping - // the server instance, load it now - var config []byte - if *runCmdConfigFlag != "" { - var err error - config, err = ioutil.ReadFile(*runCmdConfigFlag) - if err != nil { - return caddy.ExitCodeFailedStartup, - fmt.Errorf("reading config file: %v", err) - } + // 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 @@ -167,7 +167,7 @@ func cmdRun() (int, error) { certmagic.UserAgent = "Caddy/" + cleanModVersion // start the admin endpoint along with any initial config - err := caddy.StartAdmin(config) + err = caddy.StartAdmin(config) if err != nil { return caddy.ExitCodeFailedStartup, fmt.Errorf("starting caddy administration endpoint: %v", err) @@ -226,6 +226,7 @@ func cmdStop() (int, error) { 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:]) @@ -235,11 +236,10 @@ func cmdReload() (int, error) { fmt.Errorf("no configuration to load (use --config)") } - // load the configuration file - config, err := ioutil.ReadFile(*reloadCmdConfigFlag) + // get the config in caddy's native format + config, err := loadConfig(*reloadCmdConfigFlag, *reloadCmdConfigAdapterFlag) if err != nil { - return caddy.ExitCodeFailedStartup, - fmt.Errorf("reading config file: %v", err) + return caddy.ExitCodeFailedStartup, err } // get the address of the admin listener and craft endpoint URL @@ -306,3 +306,52 @@ func cmdEnviron() (int, error) { } 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 --input ") + } + + 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]string) + 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 +} diff --git a/cmd/main.go b/cmd/main.go index 16d065b..e0a3686 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -25,6 +25,7 @@ import ( "os" "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig" ) // Main implements the main function of the caddy command. @@ -62,6 +63,7 @@ var commands = map[string]commandFunc{ "version": cmdVersion, "list-modules": cmdListModules, "environ": cmdEnviron, + "adapt-config": cmdAdaptConfig, } func usageString() string { @@ -85,3 +87,66 @@ func handlePingbackConn(conn net.Conn, expect []byte) error { } return nil } + +// loadConfig loads the config from configFile and adapts it +// using adapterName. If adapterName is specified, configFile +// must be also. It prints any warnings to stderr, and returns +// the resulting JSON config bytes. +func loadConfig(configFile, adapterName string) ([]byte, error) { + // specifying an adapter without a config file is ambiguous + if configFile == "" && adapterName != "" { + return nil, fmt.Errorf("cannot adapt config without config file (use --config)") + } + + // load initial config and adapter + var config []byte + var cfgAdapter caddyconfig.Adapter + var err error + if configFile != "" { + config, err = ioutil.ReadFile(configFile) + if err != nil { + return nil, fmt.Errorf("reading config file: %v", err) + } + } else if adapterName == "" { + // as a special case when no config file or adapter + // is specified, see if the Caddyfile adapter is + // plugged in, and if so, try using a default Caddyfile + cfgAdapter = caddyconfig.GetAdapter("caddyfile") + if cfgAdapter != nil { + config, err = ioutil.ReadFile("Caddyfile") + if err != nil && !os.IsNotExist(err) { + return nil, fmt.Errorf("reading default Caddyfile: %v", err) + } + configFile = "Caddyfile" + } + } + + // load config adapter + if adapterName != "" { + cfgAdapter = caddyconfig.GetAdapter(adapterName) + if cfgAdapter == nil { + return nil, fmt.Errorf("unrecognized config adapter: %s", adapterName) + } + } + + // adapt config + if cfgAdapter != nil { + adaptedConfig, warnings, err := cfgAdapter.Adapt(config, map[string]string{ + "filename": configFile, + // TODO: all other options... (http-port, etc...) + }) + if err != nil { + return nil, fmt.Errorf("adapting config using %s: %v", adapterName, err) + } + for _, warn := range warnings { + msg := warn.Message + if warn.Directive != "" { + msg = fmt.Sprintf("%s: %s", warn.Directive, warn.Message) + } + fmt.Printf("[WARNING][%s] %s:%d: %s", adapterName, warn.File, warn.Line, msg) + } + config = adaptedConfig + } + + return config, nil +} -- cgit v1.2.3