docker/daemon.go
1b95590d
 // +build daemon
 
 package main
 
 import (
bfed4b7c
 	"crypto/tls"
007ef161
 	"fmt"
 	"io"
 	"os"
 	"path/filepath"
96ce3a19
 	"runtime"
 	"strings"
531f4122
 	"time"
007ef161
 
6f4d8470
 	"github.com/Sirupsen/logrus"
a0bf80fe
 	apiserver "github.com/docker/docker/api/server"
6871b9b1
 	"github.com/docker/docker/autogen/dockerversion"
96ce3a19
 	"github.com/docker/docker/cli"
daced1d3
 	"github.com/docker/docker/cliconfig"
63503caf
 	"github.com/docker/docker/daemon"
9b782d3a
 	"github.com/docker/docker/daemon/logger"
96ce3a19
 	"github.com/docker/docker/opts"
1b95590d
 	flag "github.com/docker/docker/pkg/mflag"
531f4122
 	"github.com/docker/docker/pkg/pidfile"
c9f3fd3f
 	"github.com/docker/docker/pkg/signal"
52f4d09f
 	"github.com/docker/docker/pkg/system"
711e5803
 	"github.com/docker/docker/pkg/timeutils"
bfed4b7c
 	"github.com/docker/docker/pkg/tlsconfig"
afade423
 	"github.com/docker/docker/registry"
78578125
 	"github.com/docker/docker/utils"
1b95590d
 )
 
96ce3a19
 const daemonUsage = "       docker daemon [ --help | ... ]\n"
 
353b7c8e
 var (
96ce3a19
 	flDaemon              = flag.Bool([]string{"#d", "#-daemon"}, false, "Enable daemon mode (deprecated; use docker daemon)")
 	daemonCli cli.Handler = NewDaemonCli()
353b7c8e
 )
 
96ce3a19
 // TODO: remove once `-d` is retired
 func handleGlobalDaemonFlag() {
 	// This block makes sure that if the deprecated daemon flag `--daemon` is absent,
 	// then all daemon-specific flags are absent as well.
 	if !*flDaemon && daemonFlags != nil {
 		flag.CommandLine.Visit(func(fl *flag.Flag) {
 			for _, name := range fl.Names {
 				name := strings.TrimPrefix(name, "#")
 				if daemonFlags.Lookup(name) != nil {
 					// daemon flag was NOT specified, but daemon-specific flags were
 					// so let's error out
 					fmt.Fprintf(os.Stderr, "docker: the daemon flag '-%s' must follow the 'docker daemon' command.\n", name)
 					os.Exit(1)
 				}
 			}
 		})
 	}
 
 	if *flDaemon {
 		if *flHelp {
 			// We do not show the help output here, instead, we tell the user about the new daemon command,
 			// because the help output is so long they would not see the warning anyway.
 			fmt.Fprintln(os.Stderr, "Please use 'docker daemon --help' instead.")
 			os.Exit(0)
 		}
 		daemonCli.(*DaemonCli).CmdDaemon(flag.Args()...)
 		os.Exit(0)
 	}
 }
 
 func presentInHelp(usage string) string { return usage }
 func absentFromHelp(string) string      { return "" }
 
 // NewDaemonCli returns a pre-configured daemon CLI
 func NewDaemonCli() *DaemonCli {
 	daemonFlags = cli.Subcmd("daemon", nil, "Enable daemon mode", true)
 
 	// TODO(tiborvass): remove InstallFlags?
 	daemonConfig := new(daemon.Config)
 	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,
dca9e02b
 	}
353b7c8e
 }
 
d55e977c
 func migrateKey() (err error) {
007ef161
 	// Migrate trust key if exists at ~/.docker/key.json and owned by current user
daced1d3
 	oldPath := filepath.Join(cliconfig.ConfigDir(), defaultTrustKeyFile)
007ef161
 	newPath := filepath.Join(getDaemonConfDir(), defaultTrustKeyFile)
01724c1c
 	if _, statErr := os.Stat(newPath); os.IsNotExist(statErr) && currentUserIsOwner(oldPath) {
d55e977c
 		defer func() {
 			// Ensure old path is removed if no error occurred
 			if err == nil {
 				err = os.Remove(oldPath)
 			} else {
6f4d8470
 				logrus.Warnf("Key migration failed, key file not removed at %s", oldPath)
1bf8954d
 				os.Remove(newPath)
d55e977c
 			}
 		}()
 
52f4d09f
 		if err := system.MkdirAll(getDaemonConfDir(), os.FileMode(0644)); err != nil {
d55e977c
 			return fmt.Errorf("Unable to create daemon configuration directory: %s", err)
007ef161
 		}
 
 		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 {
d55e977c
 			return fmt.Errorf("error opening key file %q: %s", oldPath, err)
007ef161
 		}
d55e977c
 		defer oldFile.Close()
007ef161
 
 		if _, err := io.Copy(newFile, oldFile); err != nil {
 			return fmt.Errorf("error copying key: %s", err)
 		}
 
6f4d8470
 		logrus.Infof("Migrated key from %s to %s", oldPath, newPath)
007ef161
 	}
 
 	return nil
 }
 
96ce3a19
 // DaemonCli represents the daemon CLI.
 type DaemonCli struct {
 	*daemon.Config
 	registryOptions *registry.Options
 }
 
 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 {
 	if *flDaemon {
 		// allow legacy forms `docker -D -d` and `docker -d -D`
 		logrus.Warn("please use 'docker daemon' instead.")
 	} else if !commonFlags.FlagSet.IsEmpty() || !clientFlags.FlagSet.IsEmpty() {
 		// 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`
 		flag.Merge(daemonFlags, commonFlags.FlagSet)
 	}
 
 	daemonFlags.ParseFlags(args, true)
 	commonFlags.PostParse()
 
 	if len(commonFlags.Hosts) == 0 {
 		commonFlags.Hosts = []string{opts.DefaultHost}
 	}
 	if commonFlags.TrustKey == "" {
 		commonFlags.TrustKey = filepath.Join(getDaemonConfDir(), defaultTrustKeyFile)
78578125
 	}
 
96ce3a19
 	if utils.ExperimentalBuild() {
 		logrus.Warn("Running experimental build")
1b95590d
 	}
711e5803
 
 	logrus.SetFormatter(&logrus.TextFormatter{TimestampFormat: timeutils.RFC3339NanoFixed})
 
6578ad90
 	if err := setDefaultUmask(); err != nil {
 		logrus.Fatalf("Failed to set umask: %v", err)
 	}
 
96ce3a19
 	if len(cli.LogConfig.Config) > 0 {
 		if err := logger.ValidateLogOpts(cli.LogConfig.Type, cli.LogConfig.Config); err != nil {
9b782d3a
 			logrus.Fatalf("Failed to set log opts: %v", err)
 		}
 	}
 
531f4122
 	var pfile *pidfile.PidFile
96ce3a19
 	if cli.Pidfile != "" {
 		pf, err := pidfile.New(cli.Pidfile)
531f4122
 		if err != nil {
 			logrus.Fatalf("Error starting daemon: %v", err)
 		}
 		pfile = pf
 		defer func() {
 			if err := pfile.Remove(); err != nil {
 				logrus.Error(err)
 			}
 		}()
 	}
afade423
 
96ce3a19
 	if cli.LogConfig.Config == nil {
 		cli.LogConfig.Config = make(map[string]string)
 	}
 
a0bf80fe
 	serverConfig := &apiserver.ServerConfig{
 		Logging:     true,
96ce3a19
 		EnableCors:  cli.EnableCors,
 		CorsHeaders: cli.CorsHeaders,
a0bf80fe
 		Version:     dockerversion.VERSION,
bfed4b7c
 	}
96ce3a19
 	serverConfig = setPlatformServerConfig(serverConfig, cli.Config)
bfed4b7c
 
96ce3a19
 	if commonFlags.TLSOptions != nil {
 		if !commonFlags.TLSOptions.InsecureSkipVerify {
 			// server requires and verifies client's certificate
 			commonFlags.TLSOptions.ClientAuth = tls.RequireAndVerifyClientCert
bfed4b7c
 		}
96ce3a19
 		tlsConfig, err := tlsconfig.Server(*commonFlags.TLSOptions)
bfed4b7c
 		if err != nil {
559043b9
 			logrus.Fatal(err)
bfed4b7c
 		}
 		serverConfig.TLSConfig = tlsConfig
a0bf80fe
 	}
 
531f4122
 	api := apiserver.New(serverConfig)
d9ed3165
 
a0bf80fe
 	// The serve API routine never exits unless an error occurs
459e58ff
 	// We need to start it as a goroutine and wait on it so
 	// daemon doesn't exit
 	serveAPIWait := make(chan error)
 	go func() {
96ce3a19
 		if err := api.ServeApi(commonFlags.Hosts); err != nil {
6f4d8470
 			logrus.Errorf("ServeAPI error: %v", err)
459e58ff
 			serveAPIWait <- err
 			return
 		}
 		serveAPIWait <- nil
 	}()
0e3f2f2a
 
08230703
 	if err := migrateKey(); err != nil {
 		logrus.Fatal(err)
 	}
96ce3a19
 	cli.TrustKeyPath = commonFlags.TrustKey
08230703
 
96ce3a19
 	registryService := registry.NewService(cli.registryOptions)
 	d, err := daemon.NewDaemon(cli.Config, registryService)
08230703
 	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{
 		"version":     dockerversion.VERSION,
 		"commit":      dockerversion.GITCOMMIT,
 		"execdriver":  d.ExecutionDriver().Name(),
 		"graphdriver": d.GraphDriver().String(),
 	}).Info("Docker daemon")
 
531f4122
 	signal.Trap(func() {
 		api.Close()
 		<-serveAPIWait
 		shutdownDaemon(d, 15)
 		if pfile != nil {
 			if err := pfile.Remove(); err != nil {
 				logrus.Error(err)
 			}
 		}
 	})
181fea24
 
 	// after the daemon is done setting up we can tell the api to start
d9ed3165
 	// accepting connections with specified daemon
 	api.AcceptConnections(d)
181fea24
 
459e58ff
 	// Daemon is fully initialized and handling API traffic
531f4122
 	// Wait for serve API to complete
459e58ff
 	errAPI := <-serveAPIWait
531f4122
 	shutdownDaemon(d, 15)
459e58ff
 	if errAPI != nil {
531f4122
 		if pfile != nil {
 			if err := pfile.Remove(); err != nil {
 				logrus.Error(err)
 			}
 		}
6f4d8470
 		logrus.Fatalf("Shutting down due to ServeAPI error: %v", errAPI)
459e58ff
 	}
96ce3a19
 	return nil
1b95590d
 }
01724c1c
 
531f4122
 // 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
 func shutdownDaemon(d *daemon.Daemon, timeout time.Duration) {
 	ch := make(chan struct{})
 	go func() {
 		d.Shutdown()
 		close(ch)
 	}()
 	select {
 	case <-ch:
 		logrus.Debug("Clean shutdown succeded")
 	case <-time.After(timeout * time.Second):
 		logrus.Error("Force shutdown daemon")
 	}
 }
96ce3a19
 
 func getDaemonConfDir() string {
 	// TODO: update for Windows daemon
 	if runtime.GOOS == "windows" {
 		return cliconfig.ConfigDir()
 	}
 	return "/etc/docker"
 }