github.com/kata-containers/runtime@v0.0.0-20210505125100-04f29832a923/cli/main.go (about)

     1  // Copyright (c) 2014,2015,2016 Docker, Inc.
     2  // Copyright (c) 2017-2018 Intel Corporation
     3  //
     4  // SPDX-License-Identifier: Apache-2.0
     5  //
     6  
     7  package main
     8  
     9  import (
    10  	"context"
    11  	"errors"
    12  	"fmt"
    13  	"io"
    14  	"os"
    15  	"os/signal"
    16  	goruntime "runtime"
    17  	"strings"
    18  	"syscall"
    19  
    20  	"github.com/kata-containers/runtime/pkg/katautils"
    21  	"github.com/kata-containers/runtime/pkg/signals"
    22  	vc "github.com/kata-containers/runtime/virtcontainers"
    23  	exp "github.com/kata-containers/runtime/virtcontainers/experimental"
    24  	vf "github.com/kata-containers/runtime/virtcontainers/factory"
    25  	tl "github.com/kata-containers/runtime/virtcontainers/factory/template"
    26  	"github.com/kata-containers/runtime/virtcontainers/pkg/oci"
    27  	"github.com/kata-containers/runtime/virtcontainers/pkg/rootless"
    28  	specs "github.com/opencontainers/runtime-spec/specs-go"
    29  	opentracing "github.com/opentracing/opentracing-go"
    30  	"github.com/sirupsen/logrus"
    31  	"github.com/urfave/cli"
    32  )
    33  
    34  // specConfig is the name of the file holding the containers configuration
    35  const specConfig = "config.json"
    36  
    37  // arch is the architecture for the running program
    38  const arch = goruntime.GOARCH
    39  
    40  var usage = fmt.Sprintf(`%s runtime
    41  
    42  %s is a command line program for running applications packaged
    43  according to the Open Container Initiative (OCI).`, name, name)
    44  
    45  var notes = fmt.Sprintf(`
    46  NOTES:
    47  
    48  - Commands starting "%s-" and options starting "--%s-" are `+project+` extensions.
    49  
    50  URL:
    51  
    52    The canonical URL for this project is: %s
    53  
    54  `, projectPrefix, projectPrefix, projectURL)
    55  
    56  // kataLog is the logger used to record all messages
    57  var kataLog *logrus.Entry
    58  
    59  // originalLoggerLevel is the default log level. It is used to revert the
    60  // current log level back to its original value if debug output is not
    61  // required. We set the default to 'Warn' for the runtime.
    62  var originalLoggerLevel = logrus.WarnLevel
    63  
    64  var debug = false
    65  
    66  // if true, coredump when an internal error occurs or a fatal signal is received
    67  var crashOnError = false
    68  
    69  // concrete virtcontainer implementation
    70  var virtcontainersImpl = &vc.VCImpl{}
    71  
    72  // vci is used to access a particular virtcontainers implementation.
    73  // Normally, it refers to the official package, but is re-assigned in
    74  // the tests to allow virtcontainers to be mocked.
    75  var vci vc.VC = virtcontainersImpl
    76  
    77  // defaultOutputFile is the default output file to write the gathered
    78  // information to.
    79  var defaultOutputFile = os.Stdout
    80  
    81  // defaultErrorFile is the default output file to write error
    82  // messages to.
    83  var defaultErrorFile = os.Stderr
    84  
    85  // runtimeFlags is the list of supported global command-line flags
    86  var runtimeFlags = []cli.Flag{
    87  	cli.StringFlag{
    88  		Name:  configFilePathOption,
    89  		Usage: project + " config file path",
    90  	},
    91  	cli.StringFlag{
    92  		Name:  "log",
    93  		Value: "/dev/null",
    94  		Usage: "set the log file path where internal debug information is written",
    95  	},
    96  	cli.StringFlag{
    97  		Name:  "log-format",
    98  		Value: "text",
    99  		Usage: "set the format used by logs ('text' (default), or 'json')",
   100  	},
   101  	cli.StringFlag{
   102  		Name:  "root",
   103  		Value: defaultRootDirectory,
   104  		Usage: "root directory for storage of container state (this should be located in tmpfs)",
   105  	},
   106  	cli.StringFlag{
   107  		Name:  "rootless",
   108  		Value: "auto",
   109  		Usage: "ignore cgroup permission errors ('true', 'false', or 'auto')",
   110  	},
   111  	cli.BoolFlag{
   112  		Name:  showConfigPathsOption,
   113  		Usage: "show config file paths that will be checked for (in order)",
   114  	},
   115  	cli.BoolFlag{
   116  		Name:  "systemd-cgroup",
   117  		Usage: "enable systemd cgroup support, expects cgroupsPath to be of form \"slice:prefix:name\" for e.g. \"system.slice:runc:434234\"",
   118  	},
   119  }
   120  
   121  // runtimeCommands is the list of supported command-line (sub-)
   122  // commands.
   123  var runtimeCommands = []cli.Command{
   124  	createCLICommand,
   125  	deleteCLICommand,
   126  	execCLICommand,
   127  	killCLICommand,
   128  	listCLICommand,
   129  	pauseCLICommand,
   130  	psCLICommand,
   131  	resumeCLICommand,
   132  	runCLICommand,
   133  	specCLICommand,
   134  	startCLICommand,
   135  	stateCLICommand,
   136  	updateCLICommand,
   137  	eventsCLICommand,
   138  	versionCLICommand,
   139  
   140  	// Kata Containers specific extensions
   141  	kataCheckCLICommand,
   142  	kataEnvCLICommand,
   143  	kataNetworkCLICommand,
   144  	kataOverheadCLICommand,
   145  	factoryCLICommand,
   146  }
   147  
   148  // runtimeBeforeSubcommands is the function to run before command-line
   149  // parsing occurs.
   150  var runtimeBeforeSubcommands = beforeSubcommands
   151  
   152  // runtimeAfterSubcommands is the function to run after the command-line
   153  // has been parsed.
   154  var runtimeAfterSubcommands = afterSubcommands
   155  
   156  // runtimeCommandNotFound is the function to handle an invalid sub-command.
   157  var runtimeCommandNotFound = commandNotFound
   158  
   159  // runtimeVersion is the function that returns the full version
   160  // string describing the runtime.
   161  var runtimeVersion = makeVersionString
   162  
   163  // saved default cli package values (for testing).
   164  var savedCLIAppHelpTemplate = cli.AppHelpTemplate
   165  var savedCLIVersionPrinter = cli.VersionPrinter
   166  var savedCLIErrWriter = cli.ErrWriter
   167  
   168  func init() {
   169  	kataLog = logrus.WithFields(logrus.Fields{
   170  		"name":   name,
   171  		"source": "runtime",
   172  		"arch":   arch,
   173  		"pid":    os.Getpid(),
   174  	})
   175  
   176  	// Save the original log level and then set to debug level to ensure
   177  	// that any problems detected before the config file is parsed are
   178  	// logged. This is required since the config file determines the true
   179  	// log level for the runtime: once parsed the log level is set
   180  	// appropriately but for issues between now and completion of the
   181  	// config file parsing, it is prudent to operate in verbose mode.
   182  	originalLoggerLevel = kataLog.Logger.Level
   183  	kataLog.Logger.Level = logrus.DebugLevel
   184  }
   185  
   186  // setupSignalHandler sets up signal handling, starting a go routine to deal
   187  // with signals as they arrive.
   188  //
   189  // Note that the specified context is NOT used to create a trace span (since the
   190  // first (root) span must be created in beforeSubcommands()): it is simply
   191  // used to pass to the crash handling functions to finalise tracing.
   192  func setupSignalHandler(ctx context.Context) {
   193  	signals.SetLogger(kataLog)
   194  
   195  	sigCh := make(chan os.Signal, 8)
   196  
   197  	for _, sig := range signals.HandledSignals() {
   198  		signal.Notify(sigCh, sig)
   199  	}
   200  
   201  	dieCb := func() {
   202  		katautils.StopTracing(ctx)
   203  	}
   204  
   205  	go func() {
   206  		for {
   207  			sig := <-sigCh
   208  
   209  			nativeSignal, ok := sig.(syscall.Signal)
   210  			if !ok {
   211  				err := errors.New("unknown signal")
   212  				kataLog.WithError(err).WithField("signal", sig.String()).Error()
   213  				continue
   214  			}
   215  
   216  			if signals.FatalSignal(nativeSignal) {
   217  				kataLog.WithField("signal", sig).Error("received fatal signal")
   218  				signals.Die(dieCb)
   219  			} else if debug && signals.NonFatalSignal(nativeSignal) {
   220  				kataLog.WithField("signal", sig).Debug("handling signal")
   221  				signals.Backtrace()
   222  			}
   223  		}
   224  	}()
   225  }
   226  
   227  // setExternalLoggers registers the specified logger with the external
   228  // packages which accept a logger to handle their own logging.
   229  func setExternalLoggers(ctx context.Context, logger *logrus.Entry) {
   230  	var span opentracing.Span
   231  
   232  	// Only create a new span if a root span already exists. This is
   233  	// required to ensure that this function will not disrupt the root
   234  	// span logic by creating a span before the proper root span has been
   235  	// created.
   236  
   237  	if opentracing.SpanFromContext(ctx) != nil {
   238  		span, ctx = katautils.Trace(ctx, "setExternalLoggers")
   239  		defer span.Finish()
   240  	}
   241  
   242  	// Set virtcontainers logger.
   243  	vci.SetLogger(ctx, logger)
   244  
   245  	// Set vm factory logger.
   246  	vf.SetLogger(ctx, logger)
   247  
   248  	// Set vm factory template logger.
   249  	tl.SetLogger(ctx, logger)
   250  
   251  	// Set the OCI package logger.
   252  	oci.SetLogger(ctx, logger)
   253  
   254  	// Set the katautils package logger
   255  	katautils.SetLogger(ctx, logger, originalLoggerLevel)
   256  
   257  	// Set the rootless package logger
   258  	rootless.SetLogger(ctx, logger)
   259  }
   260  
   261  // beforeSubcommands is the function to perform preliminary checks
   262  // before command-line parsing occurs.
   263  func beforeSubcommands(c *cli.Context) error {
   264  	var configFile string
   265  	var runtimeConfig oci.RuntimeConfig
   266  	var err error
   267  
   268  	katautils.SetConfigOptions(name, defaultRuntimeConfiguration, defaultSysConfRuntimeConfiguration)
   269  
   270  	handleShowConfig(c)
   271  
   272  	if userWantsUsage(c) {
   273  		// No setup required if the user just
   274  		// wants to see the usage statement.
   275  		return nil
   276  	}
   277  
   278  	r, err := parseBoolOrAuto(c.GlobalString("rootless"))
   279  	if err != nil {
   280  		return err
   281  	}
   282  	// If flag is true/false, assign the rootless flag.
   283  	// vc will not perform any auto-detection in that case.
   284  	// In case flag is nil or auto, vc detects if the runtime is running as rootless.
   285  	if r != nil {
   286  		rootless.SetRootless(*r)
   287  	}
   288  	// Support --systed-cgroup
   289  	// Issue: https://github.com/kata-containers/runtime/issues/2428
   290  
   291  	ignoreConfigLogs := false
   292  	var traceRootSpan string
   293  
   294  	subCmdIsCheckCmd := (c.NArg() >= 1 && (c.Args()[0] == checkCmd))
   295  	if subCmdIsCheckCmd {
   296  		// checkCmd will use the default logrus logger to stderr
   297  		// raise the logger default level to warn
   298  		kataLog.Logger.SetLevel(logrus.WarnLevel)
   299  		// do not print configuration logs for checkCmd
   300  		ignoreConfigLogs = true
   301  	} else {
   302  		if path := c.GlobalString("log"); path != "" {
   303  			f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_APPEND|os.O_SYNC, 0640)
   304  			if err != nil {
   305  				return err
   306  			}
   307  			kataLog.Logger.Out = f
   308  		}
   309  
   310  		switch c.GlobalString("log-format") {
   311  		case "text":
   312  			// retain logrus's default.
   313  		case "json":
   314  			kataLog.Logger.Formatter = new(logrus.JSONFormatter)
   315  		default:
   316  			return fmt.Errorf("unknown log-format %q", c.GlobalString("log-format"))
   317  		}
   318  
   319  		// Add the name of the sub-command to each log entry for easier
   320  		// debugging.
   321  		cmdName := c.Args().First()
   322  		if c.App.Command(cmdName) != nil {
   323  			kataLog = kataLog.WithField("command", cmdName)
   324  
   325  			// Name for the root span (used for tracing) now the
   326  			// sub-command name is known.
   327  			traceRootSpan = name + " " + cmdName
   328  		}
   329  
   330  		// Since a context is required, pass a new (throw-away) one - we
   331  		// cannot use the main context as tracing hasn't been enabled yet
   332  		// (meaning any spans created at this point will be silently ignored).
   333  		setExternalLoggers(context.Background(), kataLog)
   334  
   335  		if c.NArg() == 1 && c.Args()[0] == envCmd {
   336  			// simply report the logging setup
   337  			ignoreConfigLogs = true
   338  		}
   339  	}
   340  
   341  	configFile, runtimeConfig, err = katautils.LoadConfiguration(c.GlobalString(configFilePathOption), ignoreConfigLogs, false)
   342  	if err != nil {
   343  		fatal(err)
   344  	}
   345  	if !subCmdIsCheckCmd {
   346  		debug = runtimeConfig.Debug
   347  		crashOnError = runtimeConfig.Debug
   348  
   349  		if traceRootSpan != "" {
   350  			// Create the tracer.
   351  			//
   352  			// Note: no spans are created until the command-line has been parsed.
   353  			// This delays collection of trace data slightly but benefits the user by
   354  			// ensuring the first span is the name of the sub-command being
   355  			// invoked from the command-line.
   356  			err = setupTracing(c, traceRootSpan)
   357  			if err != nil {
   358  				return err
   359  			}
   360  		}
   361  	}
   362  
   363  	args := strings.Join(c.Args(), " ")
   364  
   365  	fields := logrus.Fields{
   366  		"version":   version,
   367  		"commit":    commit,
   368  		"arguments": `"` + args + `"`,
   369  	}
   370  
   371  	err = addExpFeatures(c, runtimeConfig)
   372  	if err != nil {
   373  		return err
   374  	}
   375  
   376  	kataLog.WithFields(fields).Info()
   377  
   378  	// make the data accessible to the sub-commands.
   379  	c.App.Metadata["runtimeConfig"] = runtimeConfig
   380  	c.App.Metadata["configFile"] = configFile
   381  
   382  	return nil
   383  }
   384  
   385  // handleShowConfig determines if the user wishes to see the configuration
   386  // paths. If so, it will display them and then exit.
   387  func handleShowConfig(context *cli.Context) {
   388  	if context.GlobalBool(showConfigPathsOption) {
   389  		files := katautils.GetDefaultConfigFilePaths()
   390  
   391  		for _, file := range files {
   392  			fmt.Fprintf(defaultOutputFile, "%s\n", file)
   393  		}
   394  
   395  		exit(0)
   396  	}
   397  }
   398  
   399  func setupTracing(context *cli.Context, rootSpanName string) error {
   400  	tracer, err := katautils.CreateTracer(name)
   401  	if err != nil {
   402  		fatal(err)
   403  	}
   404  
   405  	// Create the root span now that the sub-command name is
   406  	// known.
   407  	//
   408  	// Note that this "Before" function is called (and returns)
   409  	// before the subcommand handler is called. As such, we cannot
   410  	// "Finish()" the span here - that is handled in the .After
   411  	// function.
   412  	span := tracer.StartSpan(rootSpanName)
   413  
   414  	ctx, err := cliContextToContext(context)
   415  	if err != nil {
   416  		return err
   417  	}
   418  
   419  	span.SetTag("subsystem", "runtime")
   420  
   421  	// Associate the root span with the context
   422  	ctx = opentracing.ContextWithSpan(ctx, span)
   423  
   424  	// Add tracer to metadata and update the context
   425  	context.App.Metadata["tracer"] = tracer
   426  	context.App.Metadata["context"] = ctx
   427  
   428  	return nil
   429  }
   430  
   431  // add supported experimental features in context
   432  func addExpFeatures(clictx *cli.Context, runtimeConfig oci.RuntimeConfig) error {
   433  	ctx, err := cliContextToContext(clictx)
   434  	if err != nil {
   435  		return err
   436  	}
   437  
   438  	var exps []string
   439  	for _, e := range runtimeConfig.Experimental {
   440  		exps = append(exps, e.Name)
   441  	}
   442  
   443  	ctx = exp.ContextWithExp(ctx, exps)
   444  	// Add tracer to metadata and update the context
   445  	clictx.App.Metadata["context"] = ctx
   446  	return nil
   447  }
   448  
   449  func afterSubcommands(c *cli.Context) error {
   450  	ctx, err := cliContextToContext(c)
   451  	if err != nil {
   452  		return err
   453  	}
   454  
   455  	katautils.StopTracing(ctx)
   456  
   457  	return nil
   458  }
   459  
   460  // function called when an invalid command is specified which causes the
   461  // runtime to error.
   462  func commandNotFound(c *cli.Context, command string) {
   463  	err := fmt.Errorf("Invalid command %q", command)
   464  	fatal(err)
   465  }
   466  
   467  // makeVersionString returns a multi-line string describing the runtime
   468  // version along with the version of the OCI specification it supports.
   469  func makeVersionString() string {
   470  	v := make([]string, 0, 3)
   471  
   472  	versionStr := version
   473  	if versionStr == "" {
   474  		versionStr = unknown
   475  	}
   476  
   477  	v = append(v, name+"  : "+versionStr)
   478  
   479  	commitStr := commit
   480  	if commitStr == "" {
   481  		commitStr = unknown
   482  	}
   483  
   484  	v = append(v, "   commit   : "+commitStr)
   485  
   486  	specVersionStr := specs.Version
   487  	if specVersionStr == "" {
   488  		specVersionStr = unknown
   489  	}
   490  
   491  	v = append(v, "   OCI specs: "+specVersionStr)
   492  
   493  	return strings.Join(v, "\n")
   494  }
   495  
   496  // setCLIGlobals modifies various cli package global variables
   497  func setCLIGlobals() {
   498  	cli.AppHelpTemplate = fmt.Sprintf(`%s%s`, cli.AppHelpTemplate, notes)
   499  
   500  	// Override the default function to display version details to
   501  	// ensure the "--version" option and "version" command are identical.
   502  	cli.VersionPrinter = func(c *cli.Context) {
   503  		fmt.Fprintln(defaultOutputFile, c.App.Version)
   504  	}
   505  
   506  	// If the command returns an error, cli takes upon itself to print
   507  	// the error on cli.ErrWriter and exit.
   508  	// Use our own writer here to ensure the log gets sent to the right
   509  	// location.
   510  	cli.ErrWriter = &fatalWriter{cli.ErrWriter}
   511  }
   512  
   513  // createRuntimeApp creates an application to process the command-line
   514  // arguments and invoke the requested runtime command.
   515  func createRuntimeApp(ctx context.Context, args []string) error {
   516  	app := cli.NewApp()
   517  
   518  	app.Name = name
   519  	app.Writer = defaultOutputFile
   520  	app.Usage = usage
   521  	app.CommandNotFound = runtimeCommandNotFound
   522  	app.Version = runtimeVersion()
   523  	app.Flags = runtimeFlags
   524  	app.Commands = runtimeCommands
   525  	app.Before = runtimeBeforeSubcommands
   526  	app.After = runtimeAfterSubcommands
   527  	app.EnableBashCompletion = true
   528  
   529  	// allow sub-commands to access context
   530  	app.Metadata = map[string]interface{}{
   531  		"context": ctx,
   532  	}
   533  
   534  	return app.Run(args)
   535  }
   536  
   537  // userWantsUsage determines if the user only wishes to see the usage
   538  // statement.
   539  func userWantsUsage(context *cli.Context) bool {
   540  	if context.NArg() == 0 {
   541  		return true
   542  	}
   543  
   544  	if context.NArg() == 1 && (context.Args()[0] == "help" || context.Args()[0] == "version") {
   545  		return true
   546  	}
   547  
   548  	if context.NArg() >= 2 && (context.Args()[1] == "-h" || context.Args()[1] == "--help") {
   549  		return true
   550  	}
   551  
   552  	return false
   553  }
   554  
   555  // fatal prints the error's details exits the program.
   556  func fatal(err error) {
   557  	kataLog.Error(err)
   558  	fmt.Fprintln(defaultErrorFile, err)
   559  	exit(1)
   560  }
   561  
   562  type fatalWriter struct {
   563  	cliErrWriter io.Writer
   564  }
   565  
   566  func (f *fatalWriter) Write(p []byte) (n int, err error) {
   567  	// Ensure error is logged before displaying to the user
   568  	kataLog.Error(string(p))
   569  	return f.cliErrWriter.Write(p)
   570  }
   571  
   572  func createRuntime(ctx context.Context) {
   573  	setupSignalHandler(ctx)
   574  
   575  	setCLIGlobals()
   576  
   577  	err := createRuntimeApp(ctx, os.Args)
   578  	if err != nil {
   579  		fatal(err)
   580  	}
   581  }
   582  
   583  // cliContextToContext extracts the generic context from the specified
   584  // cli context.
   585  func cliContextToContext(c *cli.Context) (context.Context, error) {
   586  	if c == nil {
   587  		return nil, errors.New("need cli.Context")
   588  	}
   589  
   590  	// extract the main context
   591  	ctx, ok := c.App.Metadata["context"].(context.Context)
   592  	if !ok {
   593  		return nil, errors.New("invalid or missing context in metadata")
   594  	}
   595  
   596  	return ctx, nil
   597  }
   598  
   599  func main() {
   600  	// create a new empty context
   601  	ctx := context.Background()
   602  
   603  	dieCb := func() {
   604  		katautils.StopTracing(ctx)
   605  	}
   606  
   607  	defer signals.HandlePanic(dieCb)
   608  
   609  	createRuntime(ctx)
   610  }