2014-08-01 13:34:06 -04:00
|
|
|
// +build daemon
|
|
|
|
|
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2015-05-07 12:49:07 -04:00
|
|
|
"crypto/tls"
|
2015-01-21 19:55:05 -05:00
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
2015-05-05 00:18:28 -04:00
|
|
|
"strings"
|
2015-04-27 17:11:29 -04:00
|
|
|
"time"
|
2015-01-21 19:55:05 -05:00
|
|
|
|
2015-03-26 18:22:04 -04:00
|
|
|
"github.com/Sirupsen/logrus"
|
2015-07-29 17:47:30 -04:00
|
|
|
"github.com/docker/distribution/uuid"
|
2015-04-16 15:48:04 -04:00
|
|
|
apiserver "github.com/docker/docker/api/server"
|
2015-05-05 00:18:28 -04:00
|
|
|
"github.com/docker/docker/cli"
|
2015-04-28 11:00:18 -04:00
|
|
|
"github.com/docker/docker/cliconfig"
|
2014-08-08 05:12:39 -04:00
|
|
|
"github.com/docker/docker/daemon"
|
2015-06-30 20:40:13 -04:00
|
|
|
"github.com/docker/docker/daemon/logger"
|
2015-11-09 13:32:46 -05:00
|
|
|
"github.com/docker/docker/dockerversion"
|
2015-05-05 00:18:28 -04:00
|
|
|
"github.com/docker/docker/opts"
|
2015-12-15 14:49:41 -05:00
|
|
|
"github.com/docker/docker/pkg/jsonlog"
|
2014-08-01 13:34:06 -04:00
|
|
|
flag "github.com/docker/docker/pkg/mflag"
|
2015-04-27 17:11:29 -04:00
|
|
|
"github.com/docker/docker/pkg/pidfile"
|
2014-08-06 04:12:22 -04:00
|
|
|
"github.com/docker/docker/pkg/signal"
|
2015-06-11 14:29:29 -04:00
|
|
|
"github.com/docker/docker/pkg/system"
|
2014-08-20 11:31:24 -04:00
|
|
|
"github.com/docker/docker/registry"
|
2015-05-21 13:48:36 -04:00
|
|
|
"github.com/docker/docker/utils"
|
2015-12-29 19:27:12 -05:00
|
|
|
"github.com/docker/go-connections/tlsconfig"
|
2014-08-01 13:34:06 -04:00
|
|
|
)
|
|
|
|
|
2015-12-10 18:35:10 -05:00
|
|
|
const (
|
|
|
|
daemonUsage = " docker daemon [ --help | ... ]\n"
|
|
|
|
daemonConfigFileFlag = "-config-file"
|
|
|
|
)
|
2015-05-05 00:18:28 -04:00
|
|
|
|
2014-08-09 21:18:32 -04:00
|
|
|
var (
|
2015-05-05 00:18:28 -04:00
|
|
|
daemonCli cli.Handler = NewDaemonCli()
|
2014-08-09 21:18:32 -04:00
|
|
|
)
|
|
|
|
|
2015-12-10 18:35:10 -05:00
|
|
|
// DaemonCli represents the daemon CLI.
|
|
|
|
type DaemonCli struct {
|
|
|
|
*daemon.Config
|
|
|
|
registryOptions *registry.Options
|
|
|
|
flags *flag.FlagSet
|
|
|
|
}
|
|
|
|
|
2015-05-05 00:18:28 -04:00
|
|
|
func presentInHelp(usage string) string { return usage }
|
|
|
|
func absentFromHelp(string) string { return "" }
|
|
|
|
|
|
|
|
// NewDaemonCli returns a pre-configured daemon CLI
|
|
|
|
func NewDaemonCli() *DaemonCli {
|
2015-12-10 18:35:10 -05:00
|
|
|
daemonFlags := cli.Subcmd("daemon", nil, "Enable daemon mode", true)
|
2015-05-05 00:18:28 -04:00
|
|
|
|
|
|
|
// TODO(tiborvass): remove InstallFlags?
|
|
|
|
daemonConfig := new(daemon.Config)
|
2015-08-15 12:06:03 -04:00
|
|
|
daemonConfig.LogConfig.Config = make(map[string]string)
|
2015-09-28 19:22:57 -04:00
|
|
|
daemonConfig.ClusterOpts = make(map[string]string)
|
2015-12-10 18:35:10 -05:00
|
|
|
|
2015-05-05 00:18:28 -04:00
|
|
|
daemonConfig.InstallFlags(daemonFlags, presentInHelp)
|
|
|
|
daemonConfig.InstallFlags(flag.CommandLine, absentFromHelp)
|
|
|
|
registryOptions := new(registry.Options)
|
|
|
|
registryOptions.InstallFlags(daemonFlags, presentInHelp)
|
|
|
|
registryOptions.InstallFlags(flag.CommandLine, absentFromHelp)
|
|
|
|
daemonFlags.Require(flag.Exact, 0)
|
|
|
|
|
|
|
|
return &DaemonCli{
|
|
|
|
Config: daemonConfig,
|
|
|
|
registryOptions: registryOptions,
|
2015-12-10 18:35:10 -05:00
|
|
|
flags: daemonFlags,
|
2015-05-04 17:39:48 -04:00
|
|
|
}
|
2014-08-09 21:18:32 -04:00
|
|
|
}
|
|
|
|
|
2015-01-22 14:22:31 -05:00
|
|
|
func migrateKey() (err error) {
|
2015-01-21 19:55:05 -05:00
|
|
|
// Migrate trust key if exists at ~/.docker/key.json and owned by current user
|
2015-04-28 11:00:18 -04:00
|
|
|
oldPath := filepath.Join(cliconfig.ConfigDir(), defaultTrustKeyFile)
|
2015-01-21 19:55:05 -05:00
|
|
|
newPath := filepath.Join(getDaemonConfDir(), defaultTrustKeyFile)
|
2015-03-29 15:48:52 -04:00
|
|
|
if _, statErr := os.Stat(newPath); os.IsNotExist(statErr) && currentUserIsOwner(oldPath) {
|
2015-01-22 14:22:31 -05:00
|
|
|
defer func() {
|
|
|
|
// Ensure old path is removed if no error occurred
|
|
|
|
if err == nil {
|
|
|
|
err = os.Remove(oldPath)
|
|
|
|
} else {
|
2015-03-26 18:22:04 -04:00
|
|
|
logrus.Warnf("Key migration failed, key file not removed at %s", oldPath)
|
2015-08-03 18:29:54 -04:00
|
|
|
os.Remove(newPath)
|
2015-01-22 14:22:31 -05:00
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2015-06-11 14:29:29 -04:00
|
|
|
if err := system.MkdirAll(getDaemonConfDir(), os.FileMode(0644)); err != nil {
|
2015-01-22 14:22:31 -05:00
|
|
|
return fmt.Errorf("Unable to create daemon configuration directory: %s", err)
|
2015-01-21 19:55:05 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
newFile, err := os.OpenFile(newPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("error creating key file %q: %s", newPath, err)
|
|
|
|
}
|
|
|
|
defer newFile.Close()
|
|
|
|
|
|
|
|
oldFile, err := os.Open(oldPath)
|
|
|
|
if err != nil {
|
2015-01-22 14:22:31 -05:00
|
|
|
return fmt.Errorf("error opening key file %q: %s", oldPath, err)
|
2015-01-21 19:55:05 -05:00
|
|
|
}
|
2015-01-22 14:22:31 -05:00
|
|
|
defer oldFile.Close()
|
2015-01-21 19:55:05 -05:00
|
|
|
|
|
|
|
if _, err := io.Copy(newFile, oldFile); err != nil {
|
|
|
|
return fmt.Errorf("error copying key: %s", err)
|
|
|
|
}
|
|
|
|
|
2015-03-26 18:22:04 -04:00
|
|
|
logrus.Infof("Migrated key from %s to %s", oldPath, newPath)
|
2015-01-21 19:55:05 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-05-05 00:18:28 -04:00
|
|
|
func getGlobalFlag() (globalFlag *flag.Flag) {
|
|
|
|
defer func() {
|
|
|
|
if x := recover(); x != nil {
|
|
|
|
switch f := x.(type) {
|
|
|
|
case *flag.Flag:
|
|
|
|
globalFlag = f
|
|
|
|
default:
|
|
|
|
panic(x)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
visitor := func(f *flag.Flag) { panic(f) }
|
|
|
|
commonFlags.FlagSet.Visit(visitor)
|
|
|
|
clientFlags.FlagSet.Visit(visitor)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// CmdDaemon is the daemon command, called the raw arguments after `docker daemon`.
|
|
|
|
func (cli *DaemonCli) CmdDaemon(args ...string) error {
|
2015-07-29 17:47:30 -04:00
|
|
|
// warn from uuid package when running the daemon
|
|
|
|
uuid.Loggerf = logrus.Warnf
|
|
|
|
|
2015-11-09 09:37:24 -05:00
|
|
|
if !commonFlags.FlagSet.IsEmpty() || !clientFlags.FlagSet.IsEmpty() {
|
2015-05-05 00:18:28 -04:00
|
|
|
// deny `docker -D daemon`
|
|
|
|
illegalFlag := getGlobalFlag()
|
|
|
|
fmt.Fprintf(os.Stderr, "invalid flag '-%s'.\nSee 'docker daemon --help'.\n", illegalFlag.Names[0])
|
|
|
|
os.Exit(1)
|
|
|
|
} else {
|
|
|
|
// allow new form `docker daemon -D`
|
2015-12-10 18:35:10 -05:00
|
|
|
flag.Merge(cli.flags, commonFlags.FlagSet)
|
2015-05-05 00:18:28 -04:00
|
|
|
}
|
|
|
|
|
2015-12-10 18:35:10 -05:00
|
|
|
configFile := cli.flags.String([]string{daemonConfigFileFlag}, defaultDaemonConfigFile, "Daemon configuration file")
|
|
|
|
|
|
|
|
cli.flags.ParseFlags(args, true)
|
2015-05-05 00:18:28 -04:00
|
|
|
commonFlags.PostParse()
|
|
|
|
|
|
|
|
if commonFlags.TrustKey == "" {
|
|
|
|
commonFlags.TrustKey = filepath.Join(getDaemonConfDir(), defaultTrustKeyFile)
|
2015-05-21 13:48:36 -04:00
|
|
|
}
|
2015-12-10 18:35:10 -05:00
|
|
|
cliConfig, err := loadDaemonCliConfig(cli.Config, cli.flags, commonFlags, *configFile)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Fprint(os.Stderr, err)
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
cli.Config = cliConfig
|
|
|
|
|
|
|
|
if cli.Config.Debug {
|
|
|
|
utils.EnableDebug()
|
|
|
|
}
|
2015-05-21 13:48:36 -04:00
|
|
|
|
2015-05-05 00:18:28 -04:00
|
|
|
if utils.ExperimentalBuild() {
|
|
|
|
logrus.Warn("Running experimental build")
|
2014-08-01 13:34:06 -04:00
|
|
|
}
|
2015-03-27 21:38:00 -04:00
|
|
|
|
2015-12-15 14:49:41 -05:00
|
|
|
logrus.SetFormatter(&logrus.TextFormatter{TimestampFormat: jsonlog.RFC3339NanoFixed})
|
2015-03-27 21:38:00 -04:00
|
|
|
|
2015-06-15 09:36:19 -04:00
|
|
|
if err := setDefaultUmask(); err != nil {
|
|
|
|
logrus.Fatalf("Failed to set umask: %v", err)
|
|
|
|
}
|
|
|
|
|
2015-05-05 00:18:28 -04:00
|
|
|
if len(cli.LogConfig.Config) > 0 {
|
|
|
|
if err := logger.ValidateLogOpts(cli.LogConfig.Type, cli.LogConfig.Config); err != nil {
|
2015-06-30 20:40:13 -04:00
|
|
|
logrus.Fatalf("Failed to set log opts: %v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-25 04:35:07 -04:00
|
|
|
var pfile *pidfile.PIDFile
|
2015-05-05 00:18:28 -04:00
|
|
|
if cli.Pidfile != "" {
|
|
|
|
pf, err := pidfile.New(cli.Pidfile)
|
2015-04-27 17:11:29 -04:00
|
|
|
if err != nil {
|
|
|
|
logrus.Fatalf("Error starting daemon: %v", err)
|
|
|
|
}
|
|
|
|
pfile = pf
|
|
|
|
defer func() {
|
|
|
|
if err := pfile.Remove(); err != nil {
|
|
|
|
logrus.Error(err)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
}
|
2014-08-20 11:31:24 -04:00
|
|
|
|
2015-07-21 01:15:44 -04:00
|
|
|
serverConfig := &apiserver.Config{
|
2016-01-12 19:38:18 -05:00
|
|
|
AuthorizationPluginNames: cli.Config.AuthorizationPlugins,
|
|
|
|
Logging: true,
|
|
|
|
Version: dockerversion.Version,
|
2015-05-07 12:49:07 -04:00
|
|
|
}
|
2015-05-05 00:18:28 -04:00
|
|
|
serverConfig = setPlatformServerConfig(serverConfig, cli.Config)
|
2015-05-07 12:49:07 -04:00
|
|
|
|
2015-10-19 09:17:37 -04:00
|
|
|
defaultHost := opts.DefaultHost
|
2015-12-10 18:35:10 -05:00
|
|
|
if cli.Config.TLS {
|
|
|
|
tlsOptions := tlsconfig.Options{
|
|
|
|
CAFile: cli.Config.TLSOptions.CAFile,
|
|
|
|
CertFile: cli.Config.TLSOptions.CertFile,
|
|
|
|
KeyFile: cli.Config.TLSOptions.KeyFile,
|
|
|
|
}
|
|
|
|
|
|
|
|
if cli.Config.TLSVerify {
|
2015-05-05 00:18:28 -04:00
|
|
|
// server requires and verifies client's certificate
|
2015-12-10 18:35:10 -05:00
|
|
|
tlsOptions.ClientAuth = tls.RequireAndVerifyClientCert
|
2015-05-07 12:49:07 -04:00
|
|
|
}
|
2015-12-10 18:35:10 -05:00
|
|
|
tlsConfig, err := tlsconfig.Server(tlsOptions)
|
2015-05-07 12:49:07 -04:00
|
|
|
if err != nil {
|
2015-07-30 14:15:41 -04:00
|
|
|
logrus.Fatal(err)
|
2015-05-07 12:49:07 -04:00
|
|
|
}
|
|
|
|
serverConfig.TLSConfig = tlsConfig
|
2015-10-19 09:17:37 -04:00
|
|
|
defaultHost = opts.DefaultTLSHost
|
2015-08-21 09:28:49 -04:00
|
|
|
}
|
|
|
|
|
2015-12-10 18:35:10 -05:00
|
|
|
if len(cli.Config.Hosts) == 0 {
|
|
|
|
cli.Config.Hosts = make([]string, 1)
|
2015-10-12 04:49:25 -04:00
|
|
|
}
|
2015-12-10 18:35:10 -05:00
|
|
|
for i := 0; i < len(cli.Config.Hosts); i++ {
|
2015-08-21 09:28:49 -04:00
|
|
|
var err error
|
2015-12-10 18:35:10 -05:00
|
|
|
if cli.Config.Hosts[i], err = opts.ParseHost(defaultHost, cli.Config.Hosts[i]); err != nil {
|
|
|
|
logrus.Fatalf("error parsing -H %s : %v", cli.Config.Hosts[i], err)
|
2015-08-21 09:28:49 -04:00
|
|
|
}
|
2015-12-10 18:35:10 -05:00
|
|
|
|
|
|
|
protoAddr := cli.Config.Hosts[i]
|
2015-10-05 12:32:08 -04:00
|
|
|
protoAddrParts := strings.SplitN(protoAddr, "://", 2)
|
|
|
|
if len(protoAddrParts) != 2 {
|
|
|
|
logrus.Fatalf("bad format %s, expected PROTO://ADDR", protoAddr)
|
|
|
|
}
|
|
|
|
serverConfig.Addrs = append(serverConfig.Addrs, apiserver.Addr{Proto: protoAddrParts[0], Addr: protoAddrParts[1]})
|
|
|
|
}
|
2015-12-10 18:35:10 -05:00
|
|
|
|
2015-10-05 12:32:08 -04:00
|
|
|
api, err := apiserver.New(serverConfig)
|
|
|
|
if err != nil {
|
|
|
|
logrus.Fatal(err)
|
|
|
|
}
|
2015-04-16 15:48:04 -04:00
|
|
|
|
2015-05-07 18:35:12 -04:00
|
|
|
if err := migrateKey(); err != nil {
|
|
|
|
logrus.Fatal(err)
|
|
|
|
}
|
2015-05-05 00:18:28 -04:00
|
|
|
cli.TrustKeyPath = commonFlags.TrustKey
|
2015-05-07 18:35:12 -04:00
|
|
|
|
2015-05-05 00:18:28 -04:00
|
|
|
registryService := registry.NewService(cli.registryOptions)
|
2015-09-29 13:51:40 -04:00
|
|
|
d, err := daemon.NewDaemon(cli.Config, registryService)
|
2015-05-07 18:35:12 -04:00
|
|
|
if err != nil {
|
|
|
|
if pfile != nil {
|
|
|
|
if err := pfile.Remove(); err != nil {
|
|
|
|
logrus.Error(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
logrus.Fatalf("Error starting daemon: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
logrus.Info("Daemon has completed initialization")
|
|
|
|
|
|
|
|
logrus.WithFields(logrus.Fields{
|
2015-11-09 13:32:46 -05:00
|
|
|
"version": dockerversion.Version,
|
|
|
|
"commit": dockerversion.GitCommit,
|
2015-09-29 13:51:40 -04:00
|
|
|
"execdriver": d.ExecutionDriver().Name(),
|
2015-12-16 15:32:16 -05:00
|
|
|
"graphdriver": d.GraphDriverName(),
|
2015-05-07 18:35:12 -04:00
|
|
|
}).Info("Docker daemon")
|
|
|
|
|
2015-09-23 19:42:08 -04:00
|
|
|
api.InitRouters(d)
|
|
|
|
|
2015-12-10 18:35:10 -05:00
|
|
|
reload := func(config *daemon.Config) {
|
|
|
|
if err := d.Reload(config); err != nil {
|
|
|
|
logrus.Errorf("Error reconfiguring the daemon: %v", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
api.Reload(config)
|
|
|
|
}
|
|
|
|
|
|
|
|
setupConfigReloadTrap(*configFile, cli.flags, reload)
|
|
|
|
|
2015-11-24 21:17:37 -05:00
|
|
|
// The serve API routine never exits unless an error occurs
|
|
|
|
// We need to start it as a goroutine and wait on it so
|
|
|
|
// daemon doesn't exit
|
|
|
|
serveAPIWait := make(chan error)
|
2015-12-10 18:35:10 -05:00
|
|
|
go api.Wait(serveAPIWait)
|
2015-11-24 21:17:37 -05:00
|
|
|
|
2015-04-27 17:11:29 -04:00
|
|
|
signal.Trap(func() {
|
|
|
|
api.Close()
|
|
|
|
<-serveAPIWait
|
2015-09-29 13:51:40 -04:00
|
|
|
shutdownDaemon(d, 15)
|
2015-04-27 17:11:29 -04:00
|
|
|
if pfile != nil {
|
|
|
|
if err := pfile.Remove(); err != nil {
|
|
|
|
logrus.Error(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
2015-04-17 15:03:11 -04:00
|
|
|
|
2015-11-25 14:05:31 -05:00
|
|
|
// after the daemon is done setting up we can notify systemd api
|
2015-09-23 19:42:08 -04:00
|
|
|
notifySystem()
|
2015-04-17 15:03:11 -04:00
|
|
|
|
2015-03-11 10:33:06 -04:00
|
|
|
// Daemon is fully initialized and handling API traffic
|
2015-04-27 17:11:29 -04:00
|
|
|
// Wait for serve API to complete
|
2015-03-11 10:33:06 -04:00
|
|
|
errAPI := <-serveAPIWait
|
2015-09-29 13:51:40 -04:00
|
|
|
shutdownDaemon(d, 15)
|
2015-03-11 10:33:06 -04:00
|
|
|
if errAPI != nil {
|
2015-04-27 17:11:29 -04:00
|
|
|
if pfile != nil {
|
|
|
|
if err := pfile.Remove(); err != nil {
|
|
|
|
logrus.Error(err)
|
|
|
|
}
|
|
|
|
}
|
2015-03-26 18:22:04 -04:00
|
|
|
logrus.Fatalf("Shutting down due to ServeAPI error: %v", errAPI)
|
2015-03-11 10:33:06 -04:00
|
|
|
}
|
2015-05-05 00:18:28 -04:00
|
|
|
return nil
|
2014-08-01 13:34:06 -04:00
|
|
|
}
|
2015-03-29 15:48:52 -04:00
|
|
|
|
2015-04-27 17:11:29 -04:00
|
|
|
// shutdownDaemon just wraps daemon.Shutdown() to handle a timeout in case
|
|
|
|
// d.Shutdown() is waiting too long to kill container or worst it's
|
|
|
|
// blocked there
|
2015-09-29 13:51:40 -04:00
|
|
|
func shutdownDaemon(d *daemon.Daemon, timeout time.Duration) {
|
2015-04-27 17:11:29 -04:00
|
|
|
ch := make(chan struct{})
|
|
|
|
go func() {
|
2015-09-29 13:51:40 -04:00
|
|
|
d.Shutdown()
|
2015-04-27 17:11:29 -04:00
|
|
|
close(ch)
|
|
|
|
}()
|
|
|
|
select {
|
|
|
|
case <-ch:
|
2015-08-07 18:24:18 -04:00
|
|
|
logrus.Debug("Clean shutdown succeeded")
|
2015-04-27 17:11:29 -04:00
|
|
|
case <-time.After(timeout * time.Second):
|
|
|
|
logrus.Error("Force shutdown daemon")
|
|
|
|
}
|
|
|
|
}
|
2015-12-10 18:35:10 -05:00
|
|
|
|
|
|
|
func loadDaemonCliConfig(config *daemon.Config, daemonFlags *flag.FlagSet, commonConfig *cli.CommonFlags, configFile string) (*daemon.Config, error) {
|
|
|
|
config.Debug = commonConfig.Debug
|
|
|
|
config.Hosts = commonConfig.Hosts
|
|
|
|
config.LogLevel = commonConfig.LogLevel
|
|
|
|
config.TLS = commonConfig.TLS
|
|
|
|
config.TLSVerify = commonConfig.TLSVerify
|
|
|
|
config.TLSOptions = daemon.CommonTLSOptions{}
|
|
|
|
|
|
|
|
if commonConfig.TLSOptions != nil {
|
|
|
|
config.TLSOptions.CAFile = commonConfig.TLSOptions.CAFile
|
|
|
|
config.TLSOptions.CertFile = commonConfig.TLSOptions.CertFile
|
|
|
|
config.TLSOptions.KeyFile = commonConfig.TLSOptions.KeyFile
|
|
|
|
}
|
|
|
|
|
|
|
|
if configFile != "" {
|
|
|
|
c, err := daemon.MergeDaemonConfigurations(config, daemonFlags, configFile)
|
|
|
|
if err != nil {
|
|
|
|
if daemonFlags.IsSet(daemonConfigFileFlag) || !os.IsNotExist(err) {
|
|
|
|
return nil, fmt.Errorf("unable to configure the Docker daemon with file %s: %v\n", configFile, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// the merged configuration can be nil if the config file didn't exist.
|
|
|
|
// leave the current configuration as it is if when that happens.
|
|
|
|
if c != nil {
|
|
|
|
config = c
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return config, nil
|
|
|
|
}
|