diff --git a/cmd/containerd/config.go b/cmd/containerd/command/config.go similarity index 98% rename from cmd/containerd/config.go rename to cmd/containerd/command/config.go index 54a28a722..043297126 100644 --- a/cmd/containerd/config.go +++ b/cmd/containerd/command/config.go @@ -1,4 +1,4 @@ -package main +package command import ( "io" diff --git a/cmd/containerd/config_linux.go b/cmd/containerd/command/config_linux.go similarity index 95% rename from cmd/containerd/config_linux.go rename to cmd/containerd/command/config_linux.go index 0c1e679b9..f1cbd0c3b 100644 --- a/cmd/containerd/config_linux.go +++ b/cmd/containerd/command/config_linux.go @@ -1,4 +1,4 @@ -package main +package command import ( "github.com/containerd/containerd/defaults" diff --git a/cmd/containerd/config_unsupported.go b/cmd/containerd/command/config_unsupported.go similarity index 96% rename from cmd/containerd/config_unsupported.go rename to cmd/containerd/command/config_unsupported.go index 80f4292a5..e0a3eca47 100644 --- a/cmd/containerd/config_unsupported.go +++ b/cmd/containerd/command/config_unsupported.go @@ -1,6 +1,6 @@ // +build !linux,!windows,!solaris -package main +package command import ( "github.com/containerd/containerd/defaults" diff --git a/cmd/containerd/config_windows.go b/cmd/containerd/command/config_windows.go similarity index 95% rename from cmd/containerd/config_windows.go rename to cmd/containerd/command/config_windows.go index 0c1e679b9..f1cbd0c3b 100644 --- a/cmd/containerd/config_windows.go +++ b/cmd/containerd/command/config_windows.go @@ -1,4 +1,4 @@ -package main +package command import ( "github.com/containerd/containerd/defaults" diff --git a/cmd/containerd/command/main.go b/cmd/containerd/command/main.go new file mode 100644 index 000000000..9c169c97a --- /dev/null +++ b/cmd/containerd/command/main.go @@ -0,0 +1,200 @@ +package command + +import ( + "context" + "fmt" + "io/ioutil" + golog "log" + "net" + "os" + "os/signal" + "path/filepath" + "time" + + "github.com/containerd/containerd/log" + "github.com/containerd/containerd/server" + "github.com/containerd/containerd/sys" + "github.com/containerd/containerd/version" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "github.com/urfave/cli" + gocontext "golang.org/x/net/context" + "google.golang.org/grpc/grpclog" +) + +const usage = ` + __ _ __ + _________ ____ / /_____ _(_)___ ___ _________/ / + / ___/ __ \/ __ \/ __/ __ ` + "`" + `/ / __ \/ _ \/ ___/ __ / +/ /__/ /_/ / / / / /_/ /_/ / / / / / __/ / / /_/ / +\___/\____/_/ /_/\__/\__,_/_/_/ /_/\___/_/ \__,_/ + +high performance container runtime +` + +func init() { + // Discard grpc logs so that they don't mess with our stdio + grpclog.SetLogger(golog.New(ioutil.Discard, "", golog.LstdFlags)) + + cli.VersionPrinter = func(c *cli.Context) { + fmt.Println(c.App.Name, version.Package, c.App.Version, version.Revision) + } +} + +// App returns a *cli.App instance. +func App() *cli.App { + app := cli.NewApp() + app.Name = "containerd" + app.Version = version.Version + app.Usage = usage + app.Flags = []cli.Flag{ + cli.StringFlag{ + Name: "config,c", + Usage: "path to the configuration file", + Value: defaultConfigPath, + }, + cli.StringFlag{ + Name: "log-level,l", + Usage: "set the logging level [trace, debug, info, warn, error, fatal, panic]", + }, + cli.StringFlag{ + Name: "address,a", + Usage: "address for containerd's GRPC server", + }, + cli.StringFlag{ + Name: "root", + Usage: "containerd root directory", + }, + cli.StringFlag{ + Name: "state", + Usage: "containerd state directory", + }, + } + app.Commands = []cli.Command{ + configCommand, + publishCommand, + } + app.Action = func(context *cli.Context) error { + var ( + start = time.Now() + signals = make(chan os.Signal, 2048) + serverC = make(chan *server.Server, 1) + ctx = gocontext.Background() + config = defaultConfig() + ) + + done := handleSignals(ctx, signals, serverC) + // start the signal handler as soon as we can to make sure that + // we don't miss any signals during boot + signal.Notify(signals, handledSignals...) + + if err := server.LoadConfig(context.GlobalString("config"), config); err != nil && !os.IsNotExist(err) { + return err + } + // apply flags to the config + if err := applyFlags(context, config); err != nil { + return err + } + address := config.GRPC.Address + if address == "" { + return errors.New("grpc address cannot be empty") + } + log.G(ctx).WithFields(logrus.Fields{ + "version": version.Version, + "revision": version.Revision, + }).Info("starting containerd") + + server, err := server.New(ctx, config) + if err != nil { + return err + } + serverC <- server + if config.Debug.Address != "" { + var l net.Listener + if filepath.IsAbs(config.Debug.Address) { + if l, err = sys.GetLocalListener(config.Debug.Address, config.Debug.UID, config.Debug.GID); err != nil { + return errors.Wrapf(err, "failed to get listener for debug endpoint") + } + } else { + if l, err = net.Listen("tcp", config.Debug.Address); err != nil { + return errors.Wrapf(err, "failed to get listener for debug endpoint") + } + } + serve(ctx, l, server.ServeDebug) + } + if config.Metrics.Address != "" { + l, err := net.Listen("tcp", config.Metrics.Address) + if err != nil { + return errors.Wrapf(err, "failed to get listener for metrics endpoint") + } + serve(ctx, l, server.ServeMetrics) + } + + l, err := sys.GetLocalListener(address, config.GRPC.UID, config.GRPC.GID) + if err != nil { + return errors.Wrapf(err, "failed to get listener for main endpoint") + } + serve(ctx, l, server.ServeGRPC) + + log.G(ctx).Infof("containerd successfully booted in %fs", time.Since(start).Seconds()) + <-done + return nil + } + return app +} + +func serve(ctx context.Context, l net.Listener, serveFunc func(net.Listener) error) { + path := l.Addr().String() + log.G(ctx).WithField("address", path).Info("serving...") + go func() { + defer l.Close() + if err := serveFunc(l); err != nil { + log.G(ctx).WithError(err).WithField("address", path).Fatal("serve failure") + } + }() +} + +func applyFlags(context *cli.Context, config *server.Config) error { + // the order for config vs flag values is that flags will always override + // the config values if they are set + if err := setLevel(context, config); err != nil { + return err + } + for _, v := range []struct { + name string + d *string + }{ + { + name: "root", + d: &config.Root, + }, + { + name: "state", + d: &config.State, + }, + { + name: "address", + d: &config.GRPC.Address, + }, + } { + if s := context.GlobalString(v.name); s != "" { + *v.d = s + } + } + return nil +} + +func setLevel(context *cli.Context, config *server.Config) error { + l := context.GlobalString("log-level") + if l == "" { + l = config.Debug.Level + } + if l != "" { + lvl, err := log.ParseLevel(l) + if err != nil { + return err + } + logrus.SetLevel(lvl) + } + return nil +} diff --git a/cmd/containerd/main_unix.go b/cmd/containerd/command/main_unix.go similarity index 98% rename from cmd/containerd/main_unix.go rename to cmd/containerd/command/main_unix.go index aacd1453f..dd931ff48 100644 --- a/cmd/containerd/main_unix.go +++ b/cmd/containerd/command/main_unix.go @@ -1,6 +1,6 @@ // +build linux darwin freebsd solaris -package main +package command import ( "context" diff --git a/cmd/containerd/main_windows.go b/cmd/containerd/command/main_windows.go similarity index 97% rename from cmd/containerd/main_windows.go rename to cmd/containerd/command/main_windows.go index fd4a2af1a..014f15c77 100644 --- a/cmd/containerd/main_windows.go +++ b/cmd/containerd/command/main_windows.go @@ -1,4 +1,4 @@ -package main +package command import ( "context" diff --git a/cmd/containerd/publish.go b/cmd/containerd/command/publish.go similarity index 99% rename from cmd/containerd/publish.go rename to cmd/containerd/command/publish.go index b975c0759..8c931c99c 100644 --- a/cmd/containerd/publish.go +++ b/cmd/containerd/command/publish.go @@ -1,4 +1,4 @@ -package main +package command import ( gocontext "context" diff --git a/cmd/containerd/main.go b/cmd/containerd/main.go index 6fd022068..a0f72eca4 100644 --- a/cmd/containerd/main.go +++ b/cmd/containerd/main.go @@ -1,202 +1,16 @@ package main import ( - "context" "fmt" - "io/ioutil" - golog "log" - "net" "os" - "os/signal" - "path/filepath" - "time" - "github.com/containerd/containerd/log" - "github.com/containerd/containerd/server" - "github.com/containerd/containerd/sys" - "github.com/containerd/containerd/version" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" - "github.com/urfave/cli" - gocontext "golang.org/x/net/context" - "google.golang.org/grpc/grpclog" + "github.com/containerd/containerd/cmd/containerd/command" ) -const usage = ` - __ _ __ - _________ ____ / /_____ _(_)___ ___ _________/ / - / ___/ __ \/ __ \/ __/ __ ` + "`" + `/ / __ \/ _ \/ ___/ __ / -/ /__/ /_/ / / / / /_/ /_/ / / / / / __/ / / /_/ / -\___/\____/_/ /_/\__/\__,_/_/_/ /_/\___/_/ \__,_/ - -high performance container runtime -` - -func init() { - // Discard grpc logs so that they don't mess with our stdio - grpclog.SetLogger(golog.New(ioutil.Discard, "", golog.LstdFlags)) - - cli.VersionPrinter = func(c *cli.Context) { - fmt.Println(c.App.Name, version.Package, c.App.Version, version.Revision) - } -} - func main() { - app := cli.NewApp() - app.Name = "containerd" - app.Version = version.Version - app.Usage = usage - app.Flags = []cli.Flag{ - cli.StringFlag{ - Name: "config,c", - Usage: "path to the configuration file", - Value: defaultConfigPath, - }, - cli.StringFlag{ - Name: "log-level,l", - Usage: "set the logging level [trace, debug, info, warn, error, fatal, panic]", - }, - cli.StringFlag{ - Name: "address,a", - Usage: "address for containerd's GRPC server", - }, - cli.StringFlag{ - Name: "root", - Usage: "containerd root directory", - }, - cli.StringFlag{ - Name: "state", - Usage: "containerd state directory", - }, - } - app.Commands = []cli.Command{ - configCommand, - publishCommand, - } - app.Action = func(context *cli.Context) error { - var ( - start = time.Now() - signals = make(chan os.Signal, 2048) - serverC = make(chan *server.Server, 1) - ctx = gocontext.Background() - config = defaultConfig() - ) - - done := handleSignals(ctx, signals, serverC) - // start the signal handler as soon as we can to make sure that - // we don't miss any signals during boot - signal.Notify(signals, handledSignals...) - - if err := server.LoadConfig(context.GlobalString("config"), config); err != nil && !os.IsNotExist(err) { - return err - } - // apply flags to the config - if err := applyFlags(context, config); err != nil { - return err - } - address := config.GRPC.Address - if address == "" { - return errors.New("grpc address cannot be empty") - } - log.G(ctx).WithFields(logrus.Fields{ - "version": version.Version, - "revision": version.Revision, - }).Info("starting containerd") - - server, err := server.New(ctx, config) - if err != nil { - return err - } - serverC <- server - if config.Debug.Address != "" { - var l net.Listener - if filepath.IsAbs(config.Debug.Address) { - if l, err = sys.GetLocalListener(config.Debug.Address, config.Debug.UID, config.Debug.GID); err != nil { - return errors.Wrapf(err, "failed to get listener for debug endpoint") - } - } else { - if l, err = net.Listen("tcp", config.Debug.Address); err != nil { - return errors.Wrapf(err, "failed to get listener for debug endpoint") - } - } - serve(ctx, l, server.ServeDebug) - } - if config.Metrics.Address != "" { - l, err := net.Listen("tcp", config.Metrics.Address) - if err != nil { - return errors.Wrapf(err, "failed to get listener for metrics endpoint") - } - serve(ctx, l, server.ServeMetrics) - } - - l, err := sys.GetLocalListener(address, config.GRPC.UID, config.GRPC.GID) - if err != nil { - return errors.Wrapf(err, "failed to get listener for main endpoint") - } - serve(ctx, l, server.ServeGRPC) - - log.G(ctx).Infof("containerd successfully booted in %fs", time.Since(start).Seconds()) - <-done - return nil - } + app := command.App() if err := app.Run(os.Args); err != nil { fmt.Fprintf(os.Stderr, "containerd: %s\n", err) os.Exit(1) } } - -func serve(ctx context.Context, l net.Listener, serveFunc func(net.Listener) error) { - path := l.Addr().String() - log.G(ctx).WithField("address", path).Info("serving...") - go func() { - defer l.Close() - if err := serveFunc(l); err != nil { - log.G(ctx).WithError(err).WithField("address", path).Fatal("serve failure") - } - }() -} - -func applyFlags(context *cli.Context, config *server.Config) error { - // the order for config vs flag values is that flags will always override - // the config values if they are set - if err := setLevel(context, config); err != nil { - return err - } - for _, v := range []struct { - name string - d *string - }{ - { - name: "root", - d: &config.Root, - }, - { - name: "state", - d: &config.State, - }, - { - name: "address", - d: &config.GRPC.Address, - }, - } { - if s := context.GlobalString(v.name); s != "" { - *v.d = s - } - } - return nil -} - -func setLevel(context *cli.Context, config *server.Config) error { - l := context.GlobalString("log-level") - if l == "" { - l = config.Debug.Level - } - if l != "" { - lvl, err := log.ParseLevel(l) - if err != nil { - return err - } - logrus.SetLevel(lvl) - } - return nil -}