package main import ( "errors" "fmt" "log" "os" "runtime" "strings" "github.com/containers/common/pkg/ssh" "github.com/containers/podman/v5/cmd/podman/common" "github.com/containers/podman/v5/cmd/podman/registry" "github.com/containers/podman/v5/pkg/bindings" "github.com/containers/podman/v5/pkg/domain/entities" "github.com/sirupsen/logrus" "github.com/spf13/cobra" "sigs.k8s.io/yaml" ) // HelpTemplate is the help template for podman commands // This uses the short and long options. // command should not use this. const helpTemplate = `{{.Short}} Description: {{.Long}} {{if or .Runnable .HasSubCommands}}{{.UsageString}}{{end}}` // UsageTemplate is the usage template for podman commands // This blocks the displaying of the global options. The main podman // command should not use this. const usageTemplate = `Usage:{{if (and .Runnable (not .HasAvailableSubCommands))}} {{.UseLine}}{{end}}{{if .HasAvailableSubCommands}} {{.UseLine}} [command]{{end}}{{if gt (len .Aliases) 0}} Aliases: {{.NameAndAliases}}{{end}}{{if .HasExample}} Examples: {{.Example}}{{end}}{{if .HasAvailableSubCommands}} Available Commands:{{range .Commands}}{{if (or .IsAvailableCommand (eq .Name "help"))}} {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}} Options: {{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}} {{end}} ` var ( //rootCmd = &cobra.Command{ // // In shell completion, there is `.exe` suffix on Windows. // // This does not provide the same experience across platforms // // and was mentioned in [#16499](https://github.com/containers/podman/issues/16499). // Use: strings.TrimSuffix(filepath.Base(os.Args[0]), ".exe") + " [options]", // Long: "Manage pods, containers and images", // SilenceUsage: true, // SilenceErrors: true, // TraverseChildren: true, // PersistentPreRunE: persistentPreRunE, // RunE: validate.SubCommandExists, // //PersistentPostRunE: persistentPostRunE, // Version: version.Version.String(), // DisableFlagsInUseLine: true, //} defaultLogLevel = "warn" logLevel = defaultLogLevel dockerConfig = "" debug bool requireCleanup = true // Defaults for capturing/redirecting the command output since (the) cobra is // global-hungry and doesn't allow you to attach anything that allows us to // transform the noStdout BoolVar to a string that we can assign to useStdout. noStdout = false useStdout = "" ) func Init() { // Hooks are called before PersistentPreRunE(). These hooks affect global // state and are executed after processing the command-line, but before // actually running the command. cobra.OnInitialize( stdOutHook, // Caution, this hook redirects stdout and output from any following hooks may be affected. loggingHook, syslogHook, earlyInitHook, configHook, ) // backwards compat still allow --cni-config-dir //rootCmd.Flags().SetNormalizeFunc(func(f *pflag.FlagSet, name string) pflag.NormalizedName { // if name == "cni-config-dir" { // name = "network-config-dir" // } // return pflag.NormalizedName(name) //}) //rootCmd.SetUsageTemplate(usageTemplate) } func Execute() { //if err := rootCmd.ExecuteContext(registry.Context()); err != nil { // if registry.GetExitCode() == 0 { // registry.SetExitCode(define.ExecErrorCodeGeneric) // } // if registry.IsRemote() { // if errors.As(err, &bindings.ConnectError{}) { // fmt.Fprintln(os.Stderr, "Cannot connect to Podman. Please verify your connection to the Linux system using `podman system connection list`, or try `podman machine init` and `podman machine start` to manage a new Linux VM") // } // } // fmt.Fprintln(os.Stderr, formatError(err)) //} // //_ = shutdown.Stop() // //if requireCleanup { // // The cobra post-run is not being executed in case of // // a previous error, so make sure that the engine(s) // // are correctly shutdown. // // // // See https://github.com/spf13/cobra/issues/914 // logrus.Debugf("Shutting down engines") // if engine := registry.ImageEngine(); engine != nil { // engine.Shutdown(registry.Context()) // } // if engine := registry.ContainerEngine(); engine != nil { // engine.Shutdown(registry.Context()) // } //} // //os.Exit(registry.GetExitCode()) } // readRemoteCliFlags reads cli flags related to operating podman remotely func readRemoteCliFlags(cmd *cobra.Command, podmanConfig *entities.PodmanConfig) error { conf := podmanConfig.ContainersConfDefaultsRO contextConn, host := cmd.Root().LocalFlags().Lookup("context"), cmd.Root().LocalFlags().Lookup("host") conn, url := cmd.Root().LocalFlags().Lookup("connection"), cmd.Root().LocalFlags().Lookup("url") switch { case conn != nil && conn.Changed: if contextConn != nil && contextConn.Changed { return fmt.Errorf("use of --connection and --context at the same time is not allowed") } con, err := conf.GetConnection(conn.Value.String(), false) if err != nil { return err } podmanConfig.URI = con.URI podmanConfig.Identity = con.Identity podmanConfig.MachineMode = con.IsMachine case url.Changed: podmanConfig.URI = url.Value.String() case contextConn != nil && contextConn.Changed: service := contextConn.Value.String() if service != "default" { con, err := conf.GetConnection(service, false) if err != nil { return err } podmanConfig.URI = con.URI podmanConfig.Identity = con.Identity podmanConfig.MachineMode = con.IsMachine } case host.Changed: podmanConfig.URI = host.Value.String() default: // No cli options set, in case CONTAINER_CONNECTION was set to something // invalid this contains the error, see setupRemoteConnection(). // Important so that we can show a proper useful error message but still // allow the cli overwrites (https://github.com/containers/podman/pull/22997). return podmanConfig.ConnectionError } return nil } // setupRemoteConnection returns information about the active service destination // The order of priority is: // 1. cli flags (--connection ,--url ,--context ,--host); // 2. Env variables (CONTAINER_HOST and CONTAINER_CONNECTION); // 3. ActiveService from containers.conf; // 4. RemoteURI; // Returns the name of the default connection if any. func setupRemoteConnection(podmanConfig *entities.PodmanConfig) string { conf := podmanConfig.ContainersConfDefaultsRO connEnv, hostEnv, sshkeyEnv := os.Getenv("CONTAINER_CONNECTION"), os.Getenv("CONTAINER_HOST"), os.Getenv("CONTAINER_SSHKEY") switch { case connEnv != "": con, err := conf.GetConnection(connEnv, false) if err != nil { podmanConfig.ConnectionError = err return connEnv } podmanConfig.URI = con.URI podmanConfig.Identity = con.Identity podmanConfig.MachineMode = con.IsMachine return con.Name case hostEnv != "": if sshkeyEnv != "" { podmanConfig.Identity = sshkeyEnv } podmanConfig.URI = hostEnv default: con, err := conf.GetConnection("", true) if err == nil { podmanConfig.URI = con.URI podmanConfig.Identity = con.Identity podmanConfig.MachineMode = con.IsMachine return con.Name } podmanConfig.URI = registry.DefaultAPIAddress() } return "" } func persistentPreRunE(cmd *cobra.Command, args []string) error { logrus.Debugf("Called %s.PersistentPreRunE(%s)", cmd.Name(), strings.Join(os.Args, " ")) podmanConfig := registry.PodmanConfig() log.Println(*podmanConfig) if !registry.IsRemote() { if cmd.Flag("hooks-dir").Changed { podmanConfig.ContainersConf.Engine.HooksDir.Set(podmanConfig.HooksDir) } } if err := readRemoteCliFlags(cmd, podmanConfig); err != nil { return fmt.Errorf("read cli flags: %w", err) } c := &cobra.Command{} // Prep the engines if _, err := registry.NewImageEngine(c, args); err != nil { // Note: this is gross, but it is the hand we are dealt if registry.IsRemote() && errors.As(err, &bindings.ConnectError{}) && cmd.Name() == "info" && c.Parent() == c.Root() { clientDesc, err := getClientInfo() // we eat the error here. if this fails, they just don't any client info if err == nil { b, _ := yaml.Marshal(clientDesc) fmt.Println(string(b)) } } return err } if _, err := registry.NewContainerEngine(c, args); err != nil { return err } if _, ok := os.LookupEnv("TMPDIR"); !ok { if tmpdir, err := podmanConfig.ContainersConfDefaultsRO.ImageCopyTmpDir(); err != nil { logrus.Warnf("Failed to retrieve default tmp dir: %s", err.Error()) } else { os.Setenv("TMPDIR", tmpdir) } } _, found := c.Annotations[registry.ParentNSRequired] if !registry.IsRemote() && !found { cgroupMode := "" _, noMoveProcess := c.Annotations[registry.NoMoveProcess] if flag := c.LocalFlags().Lookup("cgroups"); flag != nil { cgroupMode = flag.Value.String() } err := registry.ContainerEngine().SetupRootless(registry.Context(), noMoveProcess, cgroupMode) if err != nil { return err } } return nil } func configHook() { if dockerConfig != "" { if err := os.Setenv("DOCKER_CONFIG", dockerConfig); err != nil { fmt.Fprintf(os.Stderr, "cannot set DOCKER_CONFIG=%s: %s", dockerConfig, err.Error()) os.Exit(1) } } } func loggingHook() { var found bool if debug { if logLevel != defaultLogLevel { fmt.Fprintf(os.Stderr, "Setting --log-level and --debug is not allowed\n") os.Exit(1) } logLevel = "debug" } for _, l := range common.LogLevels { if l == strings.ToLower(logLevel) { found = true break } } if !found { fmt.Fprintf(os.Stderr, "Log Level %q is not supported, choose from: %s\n", logLevel, strings.Join(common.LogLevels, ", ")) os.Exit(1) } level, err := logrus.ParseLevel(logLevel) if err != nil { fmt.Fprint(os.Stderr, err.Error()) os.Exit(1) } logrus.SetLevel(level) if logrus.IsLevelEnabled(logrus.InfoLevel) { logrus.Infof("%s filtering at log level %s", os.Args[0], logrus.GetLevel()) } } // used for capturing podman's formatted output to some file as per the -out and -noout flags. func stdOutHook() { // if noStdOut was specified, then assign /dev/null as the standard file for output. if noStdout { useStdout = os.DevNull } // if we were given a filename for output, then open that and use it. we end up leaking // the file since it's intended to be in scope as long as our process is running. if useStdout != "" { if fd, err := os.OpenFile(useStdout, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.ModePerm); err == nil { os.Stdout = fd // if we couldn't open the file for write, then just bail with an error. } else { fmt.Fprintf(os.Stderr, "unable to open file for standard output: %s\n", err.Error()) os.Exit(1) } } } func rootFlags(cmd *cobra.Command, podmanConfig *entities.PodmanConfig) { connectionName := setupRemoteConnection(podmanConfig) lFlags := cmd.Flags() sshFlagName := "ssh" lFlags.StringVar(&podmanConfig.SSHMode, sshFlagName, string(ssh.GolangMode), "define the ssh mode") connectionFlagName := "connection" lFlags.StringP(connectionFlagName, "c", connectionName, "Connection to use for remote Podman service (CONTAINER_CONNECTION)") urlFlagName := "url" lFlags.StringVar(&podmanConfig.URI, urlFlagName, podmanConfig.URI, "URL to access Podman service (CONTAINER_HOST)") lFlags.StringVarP(&podmanConfig.URI, "host", "H", podmanConfig.URI, "Used for Docker compatibility") _ = lFlags.MarkHidden("host") lFlags.StringVar(&dockerConfig, "config", "", "Location of authentication config file") // Context option added just for compatibility with DockerCLI. lFlags.String("context", "default", "Name of the context to use to connect to the daemon (This flag is a NOOP and provided solely for scripting compatibility.)") _ = lFlags.MarkHidden("context") identityFlagName := "identity" lFlags.StringVar(&podmanConfig.Identity, identityFlagName, podmanConfig.Identity, "path to SSH identity file, (CONTAINER_SSHKEY)") // Flags that control or influence any kind of output. outFlagName := "out" lFlags.StringVar(&useStdout, outFlagName, "", "Send output (stdout) from podman to a file") lFlags.BoolVar(&noStdout, "noout", false, "do not output to stdout") _ = lFlags.MarkHidden("noout") // Superseded by --out lFlags.BoolVarP(&podmanConfig.Remote, "remote", "r", registry.IsRemote(), "Access remote Podman service") pFlags := cmd.PersistentFlags() if registry.IsRemote() { if err := lFlags.MarkHidden("remote"); err != nil { logrus.Warnf("Unable to mark --remote flag as hidden: %s", err.Error()) } podmanConfig.Remote = true } else { moduleFlagName := "module" lFlags.StringArray(moduleFlagName, nil, "Load the containers.conf(5) module") pFlags.StringVar(&podmanConfig.ContainersConf.Engine.DBBackend, "db-backend", podmanConfig.ContainersConfDefaultsRO.Engine.DBBackend, "Database backend to use") cgroupManagerFlagName := "cgroup-manager" pFlags.StringVar(&podmanConfig.ContainersConf.Engine.CgroupManager, cgroupManagerFlagName, podmanConfig.ContainersConfDefaultsRO.Engine.CgroupManager, "Cgroup manager to use (\"cgroupfs\"|\"systemd\")") pFlags.StringVar(&podmanConfig.CPUProfile, "cpu-profile", "", "Path for the cpu-profiling results") pFlags.StringVar(&podmanConfig.MemoryProfile, "memory-profile", "", "Path for the memory-profiling results") conmonFlagName := "conmon" pFlags.StringVar(&podmanConfig.ConmonPath, conmonFlagName, "", "Path of the conmon binary") // TODO (6.0): --network-cmd-path is deprecated, remove this option with the next major release // We need to find all the places that use r.config.Engine.NetworkCmdPath and remove it networkCmdPathFlagName := "network-cmd-path" pFlags.StringVar(&podmanConfig.ContainersConf.Engine.NetworkCmdPath, networkCmdPathFlagName, podmanConfig.ContainersConfDefaultsRO.Engine.NetworkCmdPath, "Path to the command for configuring the network") networkConfigDirFlagName := "network-config-dir" pFlags.StringVar(&podmanConfig.ContainersConf.Network.NetworkConfigDir, networkConfigDirFlagName, podmanConfig.ContainersConfDefaultsRO.Network.NetworkConfigDir, "Path of the configuration directory for networks") pFlags.StringVar(&podmanConfig.ContainersConf.Containers.DefaultMountsFile, "default-mounts-file", podmanConfig.ContainersConfDefaultsRO.Containers.DefaultMountsFile, "Path to default mounts file") eventsBackendFlagName := "events-backend" pFlags.StringVar(&podmanConfig.ContainersConf.Engine.EventsLogger, eventsBackendFlagName, podmanConfig.ContainersConfDefaultsRO.Engine.EventsLogger, `Events backend to use ("file"|"journald"|"none")`) hooksDirFlagName := "hooks-dir" pFlags.StringArrayVar(&podmanConfig.HooksDir, hooksDirFlagName, podmanConfig.ContainersConfDefaultsRO.Engine.HooksDir.Get(), "Set the OCI hooks directory path (may be set multiple times)") pFlags.IntVar(&podmanConfig.MaxWorks, "max-workers", (runtime.NumCPU()*3)+1, "The maximum number of workers for parallel operations") namespaceFlagName := "namespace" pFlags.StringVar(&podmanConfig.ContainersConf.Engine.Namespace, namespaceFlagName, podmanConfig.ContainersConfDefaultsRO.Engine.Namespace, "Set the libpod namespace, used to create separate views of the containers and pods on the system") _ = pFlags.MarkHidden(namespaceFlagName) networkBackendFlagName := "network-backend" pFlags.StringVar(&podmanConfig.ContainersConf.Network.NetworkBackend, networkBackendFlagName, podmanConfig.ContainersConfDefaultsRO.Network.NetworkBackend, `Network backend to use ("cni"|"netavark")`) _ = pFlags.MarkHidden(networkBackendFlagName) rootFlagName := "root" pFlags.StringVar(&podmanConfig.GraphRoot, rootFlagName, "", "Path to the graph root directory where images, containers, etc. are stored") pFlags.StringVar(&podmanConfig.RegistriesConf, "registries-conf", "", "Path to a registries.conf to use for image processing") runrootFlagName := "runroot" pFlags.StringVar(&podmanConfig.Runroot, runrootFlagName, "", "Path to the 'run directory' where all state information is stored") imageStoreFlagName := "imagestore" pFlags.StringVar(&podmanConfig.ImageStore, imageStoreFlagName, "", "Path to the 'image store', different from 'graph root', use this to split storing the image into a separate 'image store', see 'man containers-storage.conf' for details") pFlags.BoolVar(&podmanConfig.TransientStore, "transient-store", false, "Enable transient container storage") pFlags.StringArrayVar(&podmanConfig.PullOptions, "pull-option", nil, "Specify an option to change how the image is pulled") runtimeFlagName := "runtime" pFlags.StringVar(&podmanConfig.RuntimePath, runtimeFlagName, "youki", "Path to the OCI-compatible binary used to run containers.") // -s is deprecated due to conflict with -s on subcommands storageDriverFlagName := "storage-driver" pFlags.StringVar(&podmanConfig.StorageDriver, storageDriverFlagName, "", "Select which storage driver is used to manage storage of images and containers") tmpdirFlagName := "tmpdir" pFlags.StringVar(&podmanConfig.ContainersConf.Engine.TmpDir, tmpdirFlagName, podmanConfig.ContainersConfDefaultsRO.Engine.TmpDir, "Path to the tmp directory for libpod state content.\n\nNote: use the environment variable 'TMPDIR' to change the temporary storage location for container images, '/var/tmp'.\n") pFlags.BoolVar(&podmanConfig.Trace, "trace", true, "Enable opentracing output (default false)") volumePathFlagName := "volumepath" pFlags.StringVar(&podmanConfig.ContainersConf.Engine.VolumePath, volumePathFlagName, podmanConfig.ContainersConfDefaultsRO.Engine.VolumePath, "Path to the volume directory in which volume data is stored") // Hide these flags for both ABI and Tunneling for _, f := range []string{ "cpu-profile", "db-backend", "default-mounts-file", "max-workers", "memory-profile", "pull-option", "registries-conf", "trace", } { if err := pFlags.MarkHidden(f); err != nil { logrus.Warnf("Unable to mark %s flag as hidden: %s", f, err.Error()) } } } storageOptFlagName := "storage-opt" pFlags.StringArrayVar(&podmanConfig.StorageOpts, storageOptFlagName, []string{}, "Used to pass an option to the storage driver") // Override default --help information of `--help` global flag var dummyHelp bool pFlags.BoolVar(&dummyHelp, "help", false, "Help for podman") logLevelFlagName := "log-level" pFlags.StringVar(&logLevel, logLevelFlagName, logLevel, fmt.Sprintf("Log messages above specified level (%s)", strings.Join(common.LogLevels, ", "))) lFlags.BoolVarP(&debug, "debug", "D", false, "Docker compatibility, force setting of log-level") _ = lFlags.MarkHidden("debug") // Only create these flags for ABI connections if !registry.IsRemote() { runtimeflagFlagName := "runtime-flag" pFlags.StringArrayVar(&podmanConfig.RuntimeFlags, runtimeflagFlagName, []string{}, "add global flags for the container runtime") pFlags.BoolVar(&podmanConfig.Syslog, "syslog", false, "Output logging information to syslog as well as the console (default false)") } }