diff options
56 files changed, 1209 insertions, 202 deletions
diff --git a/.cirrus.yml b/.cirrus.yml index 8e26ce72e..14ba3fc6e 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -140,6 +140,10 @@ gating_task: - '/usr/local/bin/entrypoint.sh clean podman BUILDTAGS="exclude_graphdriver_devicemapper selinux seccomp"' - '/usr/local/bin/entrypoint.sh clean podman-remote-darwin' + on_failure: + master_script: '$CIRRUS_WORKING_DIR/$SCRIPT_BASE/notice_master_failure.sh' + + build_each_commit_task: depends_on: @@ -160,9 +164,12 @@ build_each_commit_task: timeout_in: 30m script: - - $SCRIPT_BASE/setup_environment.sh - - git fetch --depth $CIRRUS_CLONE_DEPTH origin $CIRRUS_BASE_BRANCH - - env GOPATH=/var/tmp/go/ make build-all-new-commits GIT_BASE_BRANCH=origin/$CIRRUS_BASE_BRANCH + - '$SCRIPT_BASE/setup_environment.sh' + - 'git fetch --depth $CIRRUS_CLONE_DEPTH origin $CIRRUS_BASE_BRANCH' + - 'env GOPATH=/var/tmp/go/ make build-all-new-commits GIT_BASE_BRANCH=origin/$CIRRUS_BASE_BRANCH' + + on_failure: + master_script: '$CIRRUS_WORKING_DIR/$SCRIPT_BASE/notice_master_failure.sh' # Update metadata on VM images referenced by this repository state @@ -186,7 +193,7 @@ meta_task: GCPPROJECT: ENCRYPTED[7c80e728e046b1c76147afd156a32c1c57d4a1ac1eab93b7e68e718c61ca8564fc61fef815952b8ae0a64e7034b8fe4f] CIRRUS_CLONE_DEPTH: 1 # source not used - script: /usr/local/bin/entrypoint.sh + script: '/usr/local/bin/entrypoint.sh' # This task does the unit and integration testing for every platform @@ -219,14 +226,12 @@ testing_task: # Every *_script runs in sequence, for each task. The name prefix is for # WebUI reference. The values may be strings... - setup_environment_script: $SCRIPT_BASE/setup_environment.sh + setup_environment_script: '$SCRIPT_BASE/setup_environment.sh' + unit_test_script: '$SCRIPT_BASE/unit_test.sh' + integration_test_script: '$SCRIPT_BASE/integration_test.sh' - # ...or lists of strings - unit_test_script: - - go version - - $SCRIPT_BASE/unit_test.sh - - integration_test_script: $SCRIPT_BASE/integration_test.sh + on_failure: + master_script: '$CIRRUS_WORKING_DIR/$SCRIPT_BASE/notice_master_failure.sh' # This task executes tests as a regular user on a system @@ -252,12 +257,15 @@ rootless_testing_task: timeout_in: 120m - setup_environment_script: $SCRIPT_BASE/setup_environment.sh + setup_environment_script: '$SCRIPT_BASE/setup_environment.sh' rootless_test_script: >- ssh $ROOTLESS_USER@localhost -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o CheckHostIP=no $CIRRUS_WORKING_DIR/$SCRIPT_BASE/rootless_test.sh + on_failure: + master_script: '$CIRRUS_WORKING_DIR/$SCRIPT_BASE/notice_master_failure.sh' + # Because system tests are stored within the repository, it is sometimes # necessary to execute them within a PR to validate changes. @@ -283,8 +291,8 @@ optional_testing_task: timeout_in: 60m - setup_environment_script: $SCRIPT_BASE/setup_environment.sh - system_test_script: $SCRIPT_BASE/system_test.sh + setup_environment_script: '$SCRIPT_BASE/setup_environment.sh' + system_test_script: '$SCRIPT_BASE/system_test.sh' # Build new cache-images for future PR testing, but only after a PR merge. @@ -317,8 +325,8 @@ cache_images_task: scopes: - compute - devstorage.full_control - environment_script: $SCRIPT_BASE/setup_environment.sh - build_vm_images_script: $SCRIPT_BASE/build_vm_images.sh + environment_script: '$SCRIPT_BASE/setup_environment.sh' + build_vm_images_script: '$SCRIPT_BASE/build_vm_images.sh' # TODO,Continuous Delivery: Automatically open a libpod PR after using 'sed' to replace # the image_names with the new (just build) images. That will @@ -331,6 +339,9 @@ cache_images_task: # - modify_cirrus_yaml_image_names.sh # - commit_and_create_upstream_pr.sh + on_failure: + master_script: '$CIRRUS_WORKING_DIR/$SCRIPT_BASE/notice_master_failure.sh' + # Post message to IRC if everything passed success_task: @@ -350,4 +361,4 @@ success_task: cpu: 1 memory: 1 - success_script: $SCRIPT_BASE/success.sh + success_script: '$SCRIPT_BASE/success.sh' @@ -27,6 +27,7 @@ CONTAINER_RUNTIME := $(shell command -v podman 2> /dev/null || echo docker) OCI_RUNTIME ?= "" BASHINSTALLDIR=${PREFIX}/share/bash-completion/completions +ZSHINSTALLDIR=${PREFIX}/share/zsh/site-functions SELINUXOPT ?= $(shell test -x /usr/sbin/selinuxenabled && selinuxenabled && echo -Z) PACKAGES ?= $(shell $(GO) list -tags "${BUILDTAGS}" ./... | grep -v github.com/containers/libpod/vendor | grep -v e2e | grep -v system ) @@ -247,6 +248,8 @@ install.config: install.completions: install ${SELINUXOPT} -d -m 755 ${BASHINSTALLDIR} install ${SELINUXOPT} -m 644 completions/bash/podman ${BASHINSTALLDIR} + install ${SELINUXOPT} -d -m 755 ${ZSHINSTALLDIR} + install ${SELINUXOPT} -m 644 completions/zsh/_podman ${ZSHINSTALLDIR} install.cni: install ${SELINUXOPT} -d -m 755 ${ETCDIR}/cni/net.d/ @@ -332,6 +335,7 @@ API.md: cmd/podman/varlink/io.podman.varlink validate.completions: completions/bash/podman . completions/bash/podman + if [ -x /bin/zsh ]; then /bin/zsh completions/zsh/_podman; fi validate: gofmt .gitvalidation validate.completions diff --git a/cmd/podman/attach.go b/cmd/podman/attach.go index 08976cdaa..86e89cfd7 100644 --- a/cmd/podman/attach.go +++ b/cmd/podman/attach.go @@ -35,7 +35,7 @@ func init() { flags := attachCommand.Flags() flags.StringVar(&attachCommand.DetachKeys, "detach-keys", "", "Override the key sequence for detaching a container. Format is a single character [a-Z] or ctrl-<value> where <value> is one of: a-z, @, ^, [, , or _") flags.BoolVar(&attachCommand.NoStdin, "no-stdin", false, "Do not attach STDIN. The default is false") - flags.BoolVar(&attachCommand.SigProxy, "sig-proxy", true, "Proxy received signals to the process (default true)") + flags.BoolVar(&attachCommand.SigProxy, "sig-proxy", true, "Proxy received signals to the process") flags.BoolVarP(&attachCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of") markFlagHiddenForRemoteClient("latest", flags) } diff --git a/cmd/podman/build.go b/cmd/podman/build.go index 72d78aff9..f0a67791a 100644 --- a/cmd/podman/build.go +++ b/cmd/podman/build.go @@ -52,7 +52,7 @@ func init() { buildCommand.SetHelpTemplate(HelpTemplate()) buildCommand.SetUsageTemplate(UsageTemplate()) flags := buildCommand.Flags() - flags.SetInterspersed(false) + flags.SetInterspersed(true) budFlags := buildahcli.GetBudFlags(&budFlagsValues) flag := budFlags.Lookup("pull") diff --git a/cmd/podman/cliconfig/config.go b/cmd/podman/cliconfig/config.go index cb9d9a338..1461c9f03 100644 --- a/cmd/podman/cliconfig/config.go +++ b/cmd/podman/cliconfig/config.go @@ -421,14 +421,15 @@ type RmiValues struct { type RunlabelValues struct { PodmanCommand Authfile string - Display bool CertDir string Creds string + Display bool Name string Opt1 string Opt2 string Opt3 string Quiet bool + Replace bool SignaturePolicy string TlsVerify bool } diff --git a/cmd/podman/commands.go b/cmd/podman/commands.go index d37af70c1..810c5a6f6 100644 --- a/cmd/podman/commands.go +++ b/cmd/podman/commands.go @@ -21,7 +21,6 @@ func getMainCommands() []*cobra.Command { &_psCommand, _loginCommand, _logoutCommand, - _logsCommand, _mountCommand, _pauseCommand, _portCommand, @@ -63,7 +62,6 @@ func getContainerSubCommands() []*cobra.Command { _execCommand, _exportCommand, _killCommand, - _logsCommand, _mountCommand, _pauseCommand, _portCommand, diff --git a/cmd/podman/common.go b/cmd/podman/common.go index b76037297..43dfdb837 100644 --- a/cmd/podman/common.go +++ b/cmd/podman/common.go @@ -313,7 +313,7 @@ func getCreateFlags(c *cliconfig.PodmanCommand) { ) createFlags.String( "image-volume", "bind", - "Tells podman how to handle the builtin image volumes. The options are: 'bind', 'tmpfs', or 'ignore' (default 'bind')", + "Tells podman how to handle the builtin image volumes. The options are: 'bind', 'tmpfs', or 'ignore'", ) createFlags.Bool( "init", false, @@ -374,7 +374,7 @@ func getCreateFlags(c *cliconfig.PodmanCommand) { ) createFlags.Int64( "memory-swappiness", -1, - "Tune container memory swappiness (0 to 100) (default -1)", + "Tune container memory swappiness (0 to 100, or -1 for system default)", ) createFlags.String( "name", "", diff --git a/cmd/podman/container.go b/cmd/podman/container.go index ce6ad8883..2e9cedbaa 100644 --- a/cmd/podman/container.go +++ b/cmd/podman/container.go @@ -53,6 +53,7 @@ var ( _containerExistsCommand, _contInspectSubCommand, _listSubCommand, + _logsCommand, } ) diff --git a/cmd/podman/export.go b/cmd/podman/export.go index e5dc410a7..92633facd 100644 --- a/cmd/podman/export.go +++ b/cmd/podman/export.go @@ -36,7 +36,7 @@ func init() { exportCommand.SetHelpTemplate(HelpTemplate()) exportCommand.SetUsageTemplate(UsageTemplate()) flags := exportCommand.Flags() - flags.StringVarP(&exportCommand.Output, "output", "o", "/dev/stdout", "Write to a file, default is STDOUT") + flags.StringVarP(&exportCommand.Output, "output", "o", "", "Write to a specified file (default: stdout, which must be redirected)") } // exportCmd saves a container to a tarball on disk @@ -60,15 +60,16 @@ func exportCmd(c *cliconfig.ExportValues) error { } output := c.Output - if runtime.Remote && (output == "/dev/stdout" || len(output) == 0) { + if runtime.Remote && len(output) == 0 { return errors.New("remote client usage must specify an output file (-o)") } - if output == "/dev/stdout" { + if len(output) == 0 { file := os.Stdout if logrus.IsTerminal(file) { return errors.Errorf("refusing to export to terminal. Use -o flag or redirect") } + output = "/dev/stdout" } if err := parse.ValidateFileName(output); err != nil { diff --git a/cmd/podman/load.go b/cmd/podman/load.go index 303c23bc7..46add699e 100644 --- a/cmd/podman/load.go +++ b/cmd/podman/load.go @@ -34,7 +34,7 @@ func init() { loadCommand.SetHelpTemplate(HelpTemplate()) loadCommand.SetUsageTemplate(UsageTemplate()) flags := loadCommand.Flags() - flags.StringVarP(&loadCommand.Input, "input", "i", "/dev/stdin", "Read from archive file, default is STDIN") + flags.StringVarP(&loadCommand.Input, "input", "i", "", "Read from specified archive file (default: stdin)") flags.BoolVarP(&loadCommand.Quiet, "quiet", "q", false, "Suppress the output") flags.StringVar(&loadCommand.SignaturePolicy, "signature-policy", "", "Pathname of signature policy file (not usually used)") @@ -64,7 +64,10 @@ func loadCmd(c *cliconfig.LoadValues) error { if runtime.Remote && len(input) == 0 { return errors.New("the remote client requires you to load via -i and a tarball") } - if input == "/dev/stdin" { + if len(input) == 0 { + input = "/dev/stdin" + c.Input = input + fi, err := os.Stdin.Stat() if err != nil { return err diff --git a/cmd/podman/login.go b/cmd/podman/login.go index 43a7d246e..4e96b43cb 100644 --- a/cmd/podman/login.go +++ b/cmd/podman/login.go @@ -45,7 +45,7 @@ func init() { flags.StringVar(&loginCommand.CertDir, "cert-dir", "", "Pathname of a directory containing TLS certificates and keys used to connect to the registry") flags.BoolVar(&loginCommand.GetLogin, "get-login", true, "Return the current login user for the registry") flags.StringVarP(&loginCommand.Password, "password", "p", "", "Password for registry") - flags.BoolVar(&loginCommand.TlsVerify, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries (default: true)") + flags.BoolVar(&loginCommand.TlsVerify, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries") flags.StringVarP(&loginCommand.Username, "username", "u", "", "Username for registry") flags.BoolVar(&loginCommand.StdinPassword, "password-stdin", false, "Take the password from stdin") diff --git a/cmd/podman/logs.go b/cmd/podman/logs.go index c3416fe57..a1b5fb4cc 100644 --- a/cmd/podman/logs.go +++ b/cmd/podman/logs.go @@ -1,27 +1,24 @@ package main import ( - "os" "time" "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/libpod" - "github.com/containers/libpod/pkg/logs" + "github.com/containers/libpod/pkg/adapter" "github.com/containers/libpod/pkg/util" "github.com/pkg/errors" - "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) var ( logsCommand cliconfig.LogsValues - logsDescription = `Retrieves logs for a container. + logsDescription = `Retrieves logs for one or more containers. This does not guarantee execution order when combined with podman run (i.e. your run may not have generated any logs at the time you execute podman logs. ` _logsCommand = &cobra.Command{ - Use: "logs [flags] CONTAINER", + Use: "logs [flags] CONTAINER [CONTAINER...]", Short: "Fetch the logs of a container", Long: logsDescription, RunE: func(cmd *cobra.Command, args []string) error { @@ -29,9 +26,19 @@ var ( logsCommand.GlobalFlags = MainGlobalOpts return logsCmd(&logsCommand) }, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) > 0 && logsCommand.Latest { + return errors.New("no containers can be specified when using 'latest'") + } + if !logsCommand.Latest && len(args) < 1 { + return errors.New("specify at least one container name or ID to log") + } + return nil + }, Example: `podman logs ctrID podman logs --tail 2 mywebserver - podman logs --follow=true --since 10m ctrID`, + podman logs --follow=true --since 10m ctrID + podman logs mywebserver mydbserver`, } ) @@ -54,20 +61,14 @@ func init() { } func logsCmd(c *cliconfig.LogsValues) error { - var ctr *libpod.Container var err error - runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) + runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } defer runtime.Shutdown(false) - args := c.InputArgs - if len(args) != 1 && !c.Latest { - return errors.Errorf("'podman logs' requires exactly one container name/ID") - } - sinceTime := time.Time{} if c.Flag("since").Changed { // parse time, error out if something is wrong @@ -78,7 +79,7 @@ func logsCmd(c *cliconfig.LogsValues) error { sinceTime = since } - opts := &logs.LogOptions{ + opts := &libpod.LogOptions{ Details: c.Details, Follow: c.Follow, Since: sinceTime, @@ -86,30 +87,5 @@ func logsCmd(c *cliconfig.LogsValues) error { Timestamps: c.Timestamps, } - if c.Latest { - ctr, err = runtime.GetLatestContainer() - } else { - ctr, err = runtime.LookupContainer(args[0]) - } - if err != nil { - return err - } - - logPath := ctr.LogPath() - - state, err := ctr.State() - if err != nil { - return err - } - - // If the log file does not exist yet and the container is in the - // Configured state, it has never been started before and no logs exist - // Exit cleanly in this case - if _, err := os.Stat(logPath); err != nil { - if state == libpod.ContainerStateConfigured { - logrus.Debugf("Container has not been started, no logs exist yet") - return nil - } - } - return logs.ReadLogs(logPath, ctr, opts) + return runtime.Log(c, opts) } diff --git a/cmd/podman/main.go b/cmd/podman/main.go index af6731d96..ef300ef75 100644 --- a/cmd/podman/main.go +++ b/cmd/podman/main.go @@ -45,6 +45,7 @@ var mainCommands = []*cobra.Command{ &_inspectCommand, _killCommand, _loadCommand, + _logsCommand, podCommand.Command, _pullCommand, _pushCommand, @@ -75,6 +76,7 @@ var cmdsNotRequiringRootless = map[*cobra.Command]bool{ _podKillCommand: true, _podStatsCommand: true, _podStopCommand: true, + _podTopCommand: true, _restartCommand: true, _rmCommand: true, _runCommand: true, @@ -115,7 +117,7 @@ func init() { rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.DefaultMountsFile, "default-mounts-file", "", "Path to default mounts file") rootCmd.PersistentFlags().MarkHidden("defaults-mount-file") rootCmd.PersistentFlags().StringSliceVar(&MainGlobalOpts.HooksDir, "hooks-dir", []string{}, "Set the OCI hooks directory path (may be set multiple times)") - rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.LogLevel, "log-level", "error", "Log messages above specified level: debug, info, warn, error (default), fatal or panic") + rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.LogLevel, "log-level", "error", "Log messages above specified level: debug, info, warn, error, fatal or panic") rootCmd.PersistentFlags().IntVar(&MainGlobalOpts.MaxWorks, "max-workers", 0, "The maximum number of workers for parallel operations") rootCmd.PersistentFlags().MarkHidden("max-workers") rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.Namespace, "namespace", "", "Set the libpod namespace, used to create separate views of the containers and pods on the system") diff --git a/cmd/podman/play_kube.go b/cmd/podman/play_kube.go index 44aa4776b..eeb1aad64 100644 --- a/cmd/podman/play_kube.go +++ b/cmd/podman/play_kube.go @@ -59,7 +59,7 @@ func init() { flags.StringVar(&playKubeCommand.Creds, "creds", "", "`Credentials` (USERNAME:PASSWORD) to use for authenticating to a registry") flags.BoolVarP(&playKubeCommand.Quiet, "quiet", "q", false, "Suppress output information when pulling images") flags.StringVar(&playKubeCommand.SignaturePolicy, "signature-policy", "", "`Pathname` of signature policy file (not usually used)") - flags.BoolVar(&playKubeCommand.TlsVerify, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries (default: true)") + flags.BoolVar(&playKubeCommand.TlsVerify, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries") } func playKubeYAMLCmd(c *cliconfig.KubePlayValues) error { diff --git a/cmd/podman/pod_inspect.go b/cmd/podman/pod_inspect.go index 79ffe2e6f..46ac30c2a 100644 --- a/cmd/podman/pod_inspect.go +++ b/cmd/podman/pod_inspect.go @@ -44,6 +44,11 @@ func podInspectCmd(c *cliconfig.PodInspectValues) error { pod *adapter.Pod ) args := c.InputArgs + + if len(args) < 1 && !c.Latest { + return errors.Errorf("you must provide the name or id of a pod") + } + runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") diff --git a/cmd/podman/pod_top.go b/cmd/podman/pod_top.go index c9a6d8822..f65d66df6 100644 --- a/cmd/podman/pod_top.go +++ b/cmd/podman/pod_top.go @@ -9,6 +9,7 @@ import ( "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/rootless" "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -53,6 +54,10 @@ func podTopCmd(c *cliconfig.PodTopValues) error { ) args := c.InputArgs + if os.Geteuid() != 0 { + rootless.SetSkipStorageSetup(true) + } + if c.ListDescriptors { descriptors, err := libpod.GetContainerPidInformationDescriptors() if err != nil { @@ -77,6 +82,27 @@ func podTopCmd(c *cliconfig.PodTopValues) error { } else { descriptors = args[1:] } + + if os.Geteuid() != 0 { + var pod *adapter.Pod + var err error + if c.Latest { + pod, err = runtime.GetLatestPod() + } else { + pod, err = runtime.LookupPod(c.InputArgs[0]) + } + if err != nil { + return errors.Wrapf(err, "unable to lookup requested container") + } + became, ret, err := runtime.JoinOrCreateRootlessPod(pod) + if err != nil { + return err + } + if became { + os.Exit(ret) + } + } + w := tabwriter.NewWriter(os.Stdout, 5, 1, 3, ' ', 0) psOutput, err := runtime.PodTop(c, descriptors) if err != nil { diff --git a/cmd/podman/pull.go b/cmd/podman/pull.go index 7986d5530..8888c5e28 100644 --- a/cmd/podman/pull.go +++ b/cmd/podman/pull.go @@ -52,7 +52,7 @@ func init() { flags.StringVar(&pullCommand.Creds, "creds", "", "`Credentials` (USERNAME:PASSWORD) to use for authenticating to a registry") flags.BoolVarP(&pullCommand.Quiet, "quiet", "q", false, "Suppress output information when pulling images") flags.StringVar(&pullCommand.SignaturePolicy, "signature-policy", "", "`Pathname` of signature policy file (not usually used)") - flags.BoolVar(&pullCommand.TlsVerify, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries (default: true)") + flags.BoolVar(&pullCommand.TlsVerify, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries") } diff --git a/cmd/podman/push.go b/cmd/podman/push.go index afc385527..a1dac24ae 100644 --- a/cmd/podman/push.go +++ b/cmd/podman/push.go @@ -54,7 +54,7 @@ func init() { flags.BoolVar(&pushCommand.RemoveSignatures, "remove-signatures", false, "Discard any pre-existing signatures in the image") flags.StringVar(&pushCommand.SignaturePolicy, "signature-policy", "", "`Pathname` of signature policy file (not usually used)") flags.StringVar(&pushCommand.SignBy, "sign-by", "", "Add a signature at the destination using the specified key") - flags.BoolVar(&pushCommand.TlsVerify, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries (default: true)") + flags.BoolVar(&pushCommand.TlsVerify, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries") } func pushCmd(c *cliconfig.PushValues) error { diff --git a/cmd/podman/run.go b/cmd/podman/run.go index a92d5d3db..32e7b3510 100644 --- a/cmd/podman/run.go +++ b/cmd/podman/run.go @@ -44,7 +44,7 @@ func init() { runCommand.SetUsageTemplate(UsageTemplate()) flags := runCommand.Flags() flags.SetInterspersed(false) - flags.Bool("sig-proxy", true, "Proxy received signals to the process (default true)") + flags.Bool("sig-proxy", true, "Proxy received signals to the process") getCreateFlags(&runCommand.PodmanCommand) } diff --git a/cmd/podman/runlabel.go b/cmd/podman/runlabel.go index 68621e095..f79aa8b0e 100644 --- a/cmd/podman/runlabel.go +++ b/cmd/podman/runlabel.go @@ -10,9 +10,11 @@ import ( "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/cmd/podman/shared" + "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/utils" "github.com/pkg/errors" + "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -45,6 +47,7 @@ func init() { flags.StringVar(&runlabelCommand.CertDir, "cert-dir", "", "`Pathname` of a directory containing TLS certificates and keys") flags.StringVar(&runlabelCommand.Creds, "creds", "", "`Credentials` (USERNAME:PASSWORD) to use for authenticating to a registry") flags.BoolVar(&runlabelCommand.Display, "display", false, "Preview the command that the label would run") + flags.BoolVar(&runlabelCommand.Replace, "replace", false, "Replace existing container with a new one from the image") flags.StringVar(&runlabelCommand.Name, "name", "", "Assign a name to the container") flags.StringVar(&runlabelCommand.Opt1, "opt1", "", "Optional parameter to pass for install") @@ -57,7 +60,7 @@ func init() { flags.BoolP("pull", "p", false, "Pull the image if it does not exist locally prior to executing the label contents") flags.BoolVarP(&runlabelCommand.Quiet, "quiet", "q", false, "Suppress output information when installing images") flags.StringVar(&runlabelCommand.SignaturePolicy, "signature-policy", "", "`Pathname` of signature policy file (not usually used)") - flags.BoolVar(&runlabelCommand.TlsVerify, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries (default: true)") + flags.BoolVar(&runlabelCommand.TlsVerify, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries") flags.MarkDeprecated("pull", "podman will pull if not found in local storage") } @@ -146,10 +149,33 @@ func runlabelCmd(c *cliconfig.RunlabelValues) error { return err } if !c.Quiet { - fmt.Printf("Command: %s\n", strings.Join(cmd, " ")) + fmt.Printf("command: %s\n", strings.Join(cmd, " ")) if c.Display { return nil } } + + // If container already exists && --replace given -- Nuke it + if c.Replace { + for i, entry := range cmd { + if entry == "--name" { + name := cmd[i+1] + ctr, err := runtime.LookupContainer(name) + if err != nil { + if errors.Cause(err) != libpod.ErrNoSuchCtr { + logrus.Debugf("Error occurred searching for container %s: %s", name, err.Error()) + return err + } + } else { + logrus.Debugf("Runlabel --replace option given. Container %s will be deleted. The new container will be named %s", ctr.ID(), name) + if err := runtime.RemoveContainer(ctx, ctr, true, false); err != nil { + return err + } + } + break + } + } + } + return utils.ExecCmdWithStdStreams(stdIn, stdOut, stdErr, env, cmd[0], cmd[1:]...) } diff --git a/cmd/podman/save.go b/cmd/podman/save.go index df016b069..c10679740 100644 --- a/cmd/podman/save.go +++ b/cmd/podman/save.go @@ -58,7 +58,7 @@ func init() { flags := saveCommand.Flags() flags.BoolVar(&saveCommand.Compress, "compress", false, "Compress tarball image layers when saving to a directory using the 'dir' transport. (default is same compression type as source)") flags.StringVar(&saveCommand.Format, "format", v2s2Archive, "Save image to oci-archive, oci-dir (directory with oci manifest type), docker-archive, docker-dir (directory with v2s2 manifest type)") - flags.StringVarP(&saveCommand.Output, "output", "o", "/dev/stdout", "Write to a file, default is STDOUT") + flags.StringVarP(&saveCommand.Output, "output", "o", "", "Write to a specified file (default: stdout, which must be redirected)") flags.BoolVarP(&saveCommand.Quiet, "quiet", "q", false, "Suppress the output") } @@ -79,14 +79,14 @@ func saveCmd(c *cliconfig.SaveValues) error { return errors.Errorf("--compress can only be set when --format is either 'oci-dir' or 'docker-dir'") } - output := c.Output - if output == "/dev/stdout" { + if len(c.Output) == 0 { fi := os.Stdout if logrus.IsTerminal(fi) { return errors.Errorf("refusing to save to terminal. Use -o flag or redirect") } + c.Output = "/dev/stdout" } - if err := parse.ValidateFileName(output); err != nil { + if err := parse.ValidateFileName(c.Output); err != nil { return err } return runtime.SaveImage(getContext(), c) diff --git a/cmd/podman/search.go b/cmd/podman/search.go index 25f5a98b7..a10b9d419 100644 --- a/cmd/podman/search.go +++ b/cmd/podman/search.go @@ -1,6 +1,7 @@ package main import ( + "reflect" "strings" "github.com/containers/buildah/pkg/formats" @@ -46,7 +47,7 @@ func init() { flags.StringVar(&searchCommand.Format, "format", "", "Change the output format to a Go template") flags.IntVar(&searchCommand.Limit, "limit", 0, "Limit the number of results") flags.BoolVar(&searchCommand.NoTrunc, "no-trunc", false, "Do not truncate the output") - flags.BoolVar(&searchCommand.TlsVerify, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries (default: true)") + flags.BoolVar(&searchCommand.TlsVerify, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries") } func searchCmd(c *cliconfig.SearchValues) error { @@ -79,7 +80,10 @@ func searchCmd(c *cliconfig.SearchValues) error { return err } format := genSearchFormat(c.Format) - out := formats.StdoutTemplateArray{Output: searchToGeneric(results), Template: format, Fields: results[0].HeaderMap()} + if len(results) == 0 { + return nil + } + out := formats.StdoutTemplateArray{Output: searchToGeneric(results), Template: format, Fields: genSearchOutputMap()} formats.Writer(out).Out() return nil } @@ -99,3 +103,16 @@ func searchToGeneric(params []image.SearchResult) (genericParams []interface{}) } return genericParams } + +func genSearchOutputMap() map[string]string { + io := image.SearchResult{} + v := reflect.Indirect(reflect.ValueOf(io)) + values := make(map[string]string) + + for i := 0; i < v.NumField(); i++ { + key := v.Type().Field(i).Name + value := key + values[key] = strings.ToUpper(splitCamelCase(value)) + } + return values +} diff --git a/cmd/podman/shared/container.go b/cmd/podman/shared/container.go index 41950928e..6826191c5 100644 --- a/cmd/podman/shared/container.go +++ b/cmd/podman/shared/container.go @@ -3,11 +3,11 @@ package shared import ( "context" "fmt" - "github.com/google/shlex" "io" "os" "path/filepath" "regexp" + "sort" "strconv" "strings" "sync" @@ -21,6 +21,7 @@ import ( "github.com/containers/libpod/pkg/util" "github.com/cri-o/ocicni/pkg/ocicni" "github.com/docker/go-units" + "github.com/google/shlex" "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -583,18 +584,93 @@ func getCgroup(spec *specs.Spec) string { return cgroup } +func comparePorts(i, j ocicni.PortMapping) bool { + if i.ContainerPort != j.ContainerPort { + return i.ContainerPort < j.ContainerPort + } + + if i.HostIP != j.HostIP { + return i.HostIP < j.HostIP + } + + if i.HostPort != j.HostPort { + return i.HostPort < j.HostPort + } + + return i.Protocol < j.Protocol +} + +// returns the group as <IP:startPort:lastPort->startPort:lastPort/Proto> +// e.g 0.0.0.0:1000-1006->1000-1006/tcp +func formatGroup(key string, start, last int32) string { + parts := strings.Split(key, "/") + groupType := parts[0] + var ip string + if len(parts) > 1 { + ip = parts[0] + groupType = parts[1] + } + group := strconv.Itoa(int(start)) + if start != last { + group = fmt.Sprintf("%s-%d", group, last) + } + if ip != "" { + group = fmt.Sprintf("%s:%s->%s", ip, group, group) + } + return fmt.Sprintf("%s/%s", group, groupType) +} + // portsToString converts the ports used to a string of the from "port1, port2" +// also groups continuous list of ports in readable format. func portsToString(ports []ocicni.PortMapping) string { + type portGroup struct { + first int32 + last int32 + } var portDisplay []string if len(ports) == 0 { return "" } + //Sort the ports, so grouping continuous ports become easy. + sort.Slice(ports, func(i, j int) bool { + return comparePorts(ports[i], ports[j]) + }) + + // portGroupMap is used for grouping continuous ports + portGroupMap := make(map[string]*portGroup) + var groupKeyList []string + for _, v := range ports { + hostIP := v.HostIP if hostIP == "" { hostIP = "0.0.0.0" } - portDisplay = append(portDisplay, fmt.Sprintf("%s:%d->%d/%s", hostIP, v.HostPort, v.ContainerPort, v.Protocol)) + // if hostPort and containerPort are not same, consider as individual port. + if v.ContainerPort != v.HostPort { + portDisplay = append(portDisplay, fmt.Sprintf("%s:%d->%d/%s", hostIP, v.HostPort, v.ContainerPort, v.Protocol)) + continue + } + + portMapKey := fmt.Sprintf("%s/%s", hostIP, v.Protocol) + + portgroup, ok := portGroupMap[portMapKey] + if !ok { + portGroupMap[portMapKey] = &portGroup{first: v.ContainerPort, last: v.ContainerPort} + // this list is required to travese portGroupMap + groupKeyList = append(groupKeyList, portMapKey) + continue + } + + if portgroup.last == (v.ContainerPort - 1) { + portgroup.last = v.ContainerPort + continue + } + } + // for each portMapKey, format group list and appned to output string + for _, portKey := range groupKeyList { + group := portGroupMap[portKey] + portDisplay = append(portDisplay, formatGroup(portKey, group.first, group.last)) } return strings.Join(portDisplay, ", ") } diff --git a/cmd/podman/start.go b/cmd/podman/start.go index e942c1ccd..cf406cf66 100644 --- a/cmd/podman/start.go +++ b/cmd/podman/start.go @@ -41,7 +41,7 @@ func init() { flags.StringVar(&startCommand.DetachKeys, "detach-keys", "", "Override the key sequence for detaching a container. Format is a single character [a-Z] or ctrl-<value> where <value> is one of: a-z, @, ^, [, , or _") flags.BoolVarP(&startCommand.Interactive, "interactive", "i", false, "Keep STDIN open even if not attached") flags.BoolVarP(&startCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of") - flags.BoolVar(&startCommand.SigProxy, "sig-proxy", true, "Proxy received signals to the process (default true if attaching, false otherwise)") + flags.BoolVar(&startCommand.SigProxy, "sig-proxy", false, "Proxy received signals to the process (default true if attaching, false otherwise)") markFlagHiddenForRemoteClient("latest", flags) } @@ -62,14 +62,10 @@ func startCmd(c *cliconfig.StartValues) error { return errors.Errorf("you cannot start and attach multiple containers at once") } - sigProxy := c.SigProxy + sigProxy := c.SigProxy || attach if sigProxy && !attach { - if c.Flag("sig-proxy").Changed { - return errors.Wrapf(libpod.ErrInvalidArg, "you cannot use sig-proxy without --attach") - } else { - sigProxy = false - } + return errors.Wrapf(libpod.ErrInvalidArg, "you cannot use sig-proxy without --attach") } runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) diff --git a/cmd/podman/tree.go b/cmd/podman/tree.go index ebda18cdb..c56e35aef 100644 --- a/cmd/podman/tree.go +++ b/cmd/podman/tree.go @@ -23,7 +23,7 @@ var ( treeDescription = "Prints layer hierarchy of an image in a tree format" _treeCommand = &cobra.Command{ - Use: "tree", + Use: "tree [flags] IMAGE", Short: treeDescription, Long: treeDescription, RunE: func(cmd *cobra.Command, args []string) error { diff --git a/cmd/podman/varlink/io.podman.varlink b/cmd/podman/varlink/io.podman.varlink index 791790e2e..517a7a2a1 100644 --- a/cmd/podman/varlink/io.podman.varlink +++ b/cmd/podman/varlink/io.podman.varlink @@ -19,6 +19,14 @@ type StringResponse ( message: string ) +type LogLine ( + device: string, + parseLogType : string, + time: string, + msg: string, + cid: string +) + # ContainerChanges describes the return struct for ListContainerChanges type ContainerChanges ( changed: []string, @@ -522,6 +530,8 @@ method ListContainerProcesses(name: string, opts: []string) -> (container: []str # capability of varlink if the client invokes it. method GetContainerLogs(name: string) -> (container: []string) +method GetContainersLogs(names: []string, follow: bool, latest: bool, since: string, tail: int, timestamps: bool) -> (log: LogLine) + # ListContainerChanges takes a name or ID of a container and returns changes between the container and # its base image. It returns a struct of changed, deleted, and added path names. method ListContainerChanges(name: string) -> (container: ContainerChanges) diff --git a/completions/bash/podman b/completions/bash/podman index d8354fa80..1976bff44 100644 --- a/completions/bash/podman +++ b/completions/bash/podman @@ -2469,6 +2469,7 @@ _podman_container_runlabel() { -h -q --quiet + --replace --tls-verify " diff --git a/completions/zsh/_podman b/completions/zsh/_podman new file mode 100644 index 000000000..530649c0c --- /dev/null +++ b/completions/zsh/_podman @@ -0,0 +1,385 @@ +#compdef podman + +# To get zsh to reread this file: unset -f _podman;rm -f ~/.zcompdump;compinit + +# On rereads, reset cache. (Not that the cacheing works, but some day it might) +unset -m '_podman_*' + +############################################################################### +# BEGIN 'podman help' parsers -- for options, subcommands, and usage + +# Run 'podman XX --help', set _podman_commands to a formatted list of cmds +_read_podman_commands() { + local line + + # Cache: the intention here is to run each 'podman help' only once. + # Unfortunately it doesn't seem to actually be working: even though + # I can see the var_ref in my shell, it's not visible here. + local _var_ref=_podman_commands_"${*// /_}" + typeset -ga _podman_commands + _podman_commands=(${(P)_var_ref}) + (( $#_podman_commands )) && return + + _call_program podman podman "$@" --help |\ + sed -n -e '0,/^Available Commands/d' -e '/^[A-Z]/q;p' |\ + sed -e 's/^ \+\([^ ]\+\) \+/\1:/' |\ + egrep . | while read line; do + _podman_commands+=($line) + done + + eval "typeset -ga $_var_ref" + eval "$_var_ref=(\$_podman_commands)" +} + +# Run 'podman XX --help', set _podman_flag_list to a formatted list +# of flag options for XX +_read_podman_flags() { + local line + + local _var_ref=_podman_flags_"${*// /_}" + eval "typeset -ga ${_var_ref}" + typeset -ga _podman_flag_list + _podman_flag_list=(${(P)_var_ref}) + (( $#_podman_flag_list )) && return + + # Extract the Flags; strip leading whitespace; pack '-f, --foo' + # as '-f,--foo' (no space); then add '=' to '--foo string'. + # The result will be, e.g. '-f,--foo=string Description of Option' + _call_program podman podman "$@" --help |\ + sed -n -e '0,/^Flags:/d' -e '/^$/q;p' |\ + sed -e 's/^ *//' -e 's/^\(-.,\) --/\1--/' |\ + sed -e 's/^\(-[^ ]\+\) \([^ ]\+\) /\1=\2 /' |\ + while read flags desc;do + # flags like --foo=string: split into --foo & string + local -a tmpa + local optval= + tmpa=(${(s.=.)flags}) + if [ -n "$tmpa[2]" ]; then + flags=$tmpa[1] + optval=$tmpa[2] + fi + + # 'podman attach --detach-keys' includes ']' in help msg + desc=${desc//\]/\\]} + + for flag in ${(s:,:)flags}; do + if [ -n "$optval" ]; then + _podman_flag_list+=("${flag}[$desc]:$(_podman_find_helper ${flags} ${optval} ${desc})") + else + _podman_flag_list+=("${flag}[$desc]") + fi + done + done + + eval "typeset -ga $_var_ref=(\$_podman_flag_list)" +} + +# Run 'podman XXX --help', set _podman_usage to the line after "Usage:" +_read_podman_usage() { + local _var_ref=_podman_usage_"${*// /_}" + typeset -ga _podman_usage + _podman_usage=${(P)_var_ref} + (( $#_podman_usage )) && return + + _podman_usage=$(_call_program podman podman "$@" --help |\ + grep -A1 'Usage:'|\ + tail -1 |\ + sed -e 's/^ *//') + + eval "typeset -ga $_var_ref" + eval "$_var_ref=\$_podman_usage" +} + +# END 'podman help' parsers +############################################################################### +# BEGIN custom helpers for individual option arguments + +# Find a zsh helper for a given flag or command-line option +_podman_find_helper() { + local flags=$1 + local optval=$2 + local desc=$3 + local helper= + + # Yes, this is a lot of hardcoding. IMHO it's still better than + # hardcoding every possible podman option. + # FIXME: there are many more options that could use helpers. + if expr "$desc" : ".*[Dd]irectory" >/dev/null; then + optval="directory" + helper="_files -/" + elif expr "$desc" : ".*[Pp]ath" >/dev/null; then + optval="path" + helper=_files + elif [ "$flags" = "--cgroup-manager" ]; then + optval="cgroup manager" + helper="(cgroupfs systemd)" + elif [ "$flags" = "--log-level" ]; then + optval="log level" + # 'Log messages above specified level: debug, ... (default "...")' + # Strip off the description and all 'default' strings + desc=${desc/Log*:/} # debug, info, ... (default "...") + desc=${(S)desc//\(*\)/} # debug, info, ... or panic + desc=${desc//,/} # debug info ... or panic + desc=${desc// or / } # debug info ... panic + desc=${desc// / } # collapse multiple spaces + # FIXME: how to present values _in order_, not sorted alphabetically? + helper="($desc)" + fi + echo "$optval:$helper" +} + +# END custom helpers for individual option arguments +############################################################################### +# BEGIN helpers for command-line args (containers, images) + +__podman_helper_generic() { + local expl line + local -a results + + local foo1=$1; shift + local name=$2; shift + + _call_program $foo1 podman "$@" |\ + while read line; do + results+=(${=line}) + done + + _wanted $foo1 expl $name compadd ${(u)results} +} + +_podman_helper_image() { + __podman_helper_generic podman-images 'images' \ + images --format '{{.ID}}\ {{.Repository}}:{{.Tag}}' +} + +# FIXME: at some point, distinguish between running & stopped containers +_podman_helper_container() { + __podman_helper_generic podman-containers 'containers' \ + ps -a --format '{{.Names}}\ {{.ID}}' +} + +_podman_helper_pod() { + __podman_helper_generic podman-pods 'pods' pod list --format '{{.Name}}' +} + +_podman_helper_volume() { + __podman_helper_generic podman-volumes 'volumes' volume ls --format '{{.Name}}' +} + +# Combinations. This one seen in diff & inspect +_podman_helper_container-or-image() { + _podman_helper_image + _podman_helper_container +} + +# Seen in generate-kube +_podman_helper_container-or-pod() { + _podman_helper_container + _podman_helper_pod +} + +# For top and pod-top +_podman_helper_format-descriptors() { + __podman_helper_generic top-format-descriptors 'format descriptors' \ + top --list-descriptors +} + +# for push, login/logout, and trust +# FIXME: some day, use this to define a helper for IMAGE-PATH (in 'pull') +_podman_helper_registry() { + local expl + local -a registries + + # Suggestions for improvement more than welcome. + python3 -c 'from configparser import ConfigParser;cp=ConfigParser();cp.read("/etc/containers/registries.conf");registries=eval(cp.get("registries.search","registries"));[print(r) for r in registries]' 2>/dev/null | while read line; do + registries+=($line) + done + + if (( $#registries )); then + _wanted podman-registry expl "registry" compadd ${(u)registries} + else + _hosts + fi +} + +# END helpers for command-line args +############################################################################### +# BEGIN figure out completion helpers for a given (sub)command + +# Read Usage string for this subcommand, set up helpers for its subargs +_set_up_podman_args() { + _read_podman_usage "$@" + + typeset -ga _podman_args=() + # E.g. 'podman exec [flags] CONTAINER [...' -> 'CONTAINER [....' + local usage_rhs=$(expr "$_podman_usage" : ".*\[flags\] \+\(.*\)") + + # e.g. podman pod ps which takes no further args + if [ -z "$usage_rhs" ]; then + return + fi + + # podman diff & inspect accept 'CONTAINER | IMAGE'; make into one keyword. + usage_rhs=${usage_rhs// | /-OR-} + + # Arg parsing. There are three possibilities in Usage messages: + # + # [IMAGE] - optional image arg (zero or one) + # IMAGE - exactly one image arg + # IMAGE [IMAGE...] - one or more image args + # and, theoretically: + # [IMAGE...] - zero or more? Haven't seen it in practice. Defer. + # + # For completion purposes, we only need to provide two options: + # one, or more than one? That is: continue offering completion + # suggestions after the first one? For that, we make two passes; + # in the first, mark an option as either '' (only one) or + + # Parse each command-line arg seen in usage message + local word + local -A _seen=() + for word in ${=usage_rhs}; do + local unbracketed=$(expr "$word" : "\[\(.*\)\]") + + if [ -n "$unbracketed" ]; then + # Remove all dots; assume(!?) that they'll all be at the end + unbracketed=${unbracketed//./} + + if (( $_seen[$unbracketed] )); then + # Is this the same word as the previous arg? + if expr "$_podman_args[-1]" : ":$unbracketed:" >/dev/null; then + # Yes. Make it '*:...' instead of ':...', indicating >1 + _podman_args[-1]="*$_podman_args[-1]" + fi + continue + fi + + word=$unbracketed + fi + + # As of 2019-03 all such instances are '[COMMAND [ARG...]]' and are, + # of course, at the end of the line. We can't offer completion for + # these, because the container will have different commands than + # the host system... but try anyway. + if [ "$word" = '[COMMAND' ]; then + # e.g. podman create, exec, run + _podman_args+=( + ":command: _command_names -e" + "*::arguments: _normal" + ) + return + fi + + # Look for an existing helper, e.g. IMAGE -> _podman_helper_image + local helper="_podman_helper_${(L)word}" + if (( $+functions[$helper] )); then + : + else + # No defined helper. Reset, but check for known expressions. + helper= + case "$word" in + KUBEFILE) helper='_files -g "*.y(|a)ml(-.)"' ;; + PATH) helper='_files' ;; + esac + fi + + # Another special case: 'top' actually takes multiple options + local multi= + if [ "$word" = "FORMAT-DESCRIPTORS" ]; then + multi='*' + fi + _podman_args+=("$multi:${(L)word}:$helper") + _seen[$word]=1 + done +} + +# For an endpoint command, i.e. not a subcommand. +_podman_terminus() { + typeset -A opt_args + typeset -ga _podman_flag_list + typeset -ga _podman_args + integer ret=1 + + # Find out what args it takes (e.g. image(s), container(s)) and see + # if we have helpers for them. + _set_up_podman_args "$@" + _arguments -C $_podman_flag_list $_podman_args && ret=0 + + return ret +} + +# END figure out completion helpers for a given (sub)command +################################################################################ +# BEGIN actual entry point + +# This is the main entry point; it's also where we (recursively) come in +# to handle nested subcommands such as 'podman container' or even 3-level +# ones like 'podman generate kube'. Nesting is complicated, so here's +# my best understanding as of 2019-03-12: +# +# Easy first: when you do "podman <TAB>" zsh calls us, we run 'podman --help', +# figure out the global options and subcommands, and run the magic _arguments +# command. That will offer those options/subcommands, and complete, etc. +# +# Where it gets harder is when you do "podman container mount <TAB>". +# zsh first calls us with words=(podman container mount) but we don't +# want all that full context yet! We want to go a piece at a time, +# handling 'container' first, then 'mount'; ending up with our +# final 'podman container mount --help' giving us suitable flags +# and no subcommands; from which we determine that it's a terminus +# and jump to a function that handles non-subcommand arguments. +# +# This is the closest I've yet come to understanding zsh completion; +# it is still incomplete and may in fact be incorrect. But it works +# better than anything I've played with so far. +_podman_subcommand() { + local curcontext="$curcontext" state line + typeset -A opt_args + integer ret=1 + + # Run 'podman --help' / 'podman system --help' for our context (initially + # empty, then possibly under subcommands); from those, get a list of + # flags appropriate for this context and, if applicable, subcommands. + _read_podman_flags "$@" + _read_podman_commands "$@" + + # Now, is this a sub-subcommand, or does it have args? + if (( $#_podman_commands )); then + # Subcommands required (podman, podman system, etc) + local cmd=${words// /_} + _arguments -C $_podman_flag_list \ + "(-): :->command" \ + "(-)*:: :->option-or-argument" \ + && ret=0 + + case $state in + (command) + _describe -t command "podman $* command" _podman_commands && ret=0 + ;; + (option-or-argument) + # I think this is when we have a _completed_ subcommand. + # Recurse back into here, offering options for that subcommand. + curcontext=${curcontext%:*:*}:podman-${words[1]}: + _podman_subcommand "$@" ${words[1]} && ret=0 + ;; + esac + else + # At a terminus, i.e. podman info, podman history; find out + # what args it takes. + _podman_terminus "$@" && ret=0 + fi + + return ret +} + +_podman() { + _podman_subcommand +} + +# Local Variables: +# mode: shell-script +# sh-indentation: 4 +# indent-tabs-mode: nil +# sh-basic-offset: 4 +# End: +# vim: ft=zsh sw=4 ts=4 et diff --git a/contrib/cirrus/lib.sh b/contrib/cirrus/lib.sh index 9419dad05..773f4f484 100644 --- a/contrib/cirrus/lib.sh +++ b/contrib/cirrus/lib.sh @@ -103,6 +103,15 @@ clean_env() { unset -v UNSET_ENV_VARS $UNSET_ENV_VARS || true # don't fail on read-only } +die() { + req_env_var " + 1 $1 + 2 $2 + " + echo "$2" + exit $1 +} + # Return a GCE image-name compatible string representation of distribution name os_release_id() { eval "$(egrep -m 1 '^ID=' /etc/os-release | tr -d \' | tr -d \")" @@ -136,14 +145,14 @@ stub() { ircmsg() { req_env_var " CIRRUS_TASK_ID $CIRRUS_TASK_ID - 1 $1 + @ $@ " # Sometimes setup_environment.sh didn't run SCRIPT="$(dirname $0)/podbot.py" NICK="podbot_$CIRRUS_TASK_ID" NICK="${NICK:0:15}" # Any longer will break things set +e - $SCRIPT $NICK $1 + $SCRIPT $NICK $@ echo "Ignoring exit($?)" set -e } diff --git a/contrib/cirrus/notice_master_failure.sh b/contrib/cirrus/notice_master_failure.sh new file mode 100755 index 000000000..4b09331d3 --- /dev/null +++ b/contrib/cirrus/notice_master_failure.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +set -e + +source $(dirname $0)/lib.sh + +# mIRC "escape" codes are the most standard, for a non-standard client-side interpretation. +ETX="$(echo -n -e '\x03')" +RED="${ETX}4" +NOR="$(echo -n -e '\x0f')" + +if [[ "$CIRRUS_BRANCH" =~ "master" ]] +then + BURL="https://cirrus-ci.com/build/$CIRRUS_BUILD_ID" + ircmsg "${RED}[Action Recommended]: ${NOR}Post-merge testing ${RED}$CIRRUS_BRANCH failed${NOR} in $CIRRUS_TASK_NAME on $(os_release_id)-$(os_release_ver): $BURL. Please investigate, and re-run if appropriate." +fi + +# This script assumed to be executed on failure +die 1 "Testing Failed" diff --git a/contrib/cirrus/setup_environment.sh b/contrib/cirrus/setup_environment.sh index d8d97904b..618027ecd 100755 --- a/contrib/cirrus/setup_environment.sh +++ b/contrib/cirrus/setup_environment.sh @@ -66,6 +66,7 @@ then RUNC="https://kojipkgs.fedoraproject.org/packages/runc/1.0.0/55.dev.git578fe65.fc${OS_RELEASE_VER}/x86_64/runc-1.0.0-55.dev.git578fe65.fc${OS_RELEASE_VER}.x86_64.rpm" echo ">>>>> OVERRIDING RUNC WITH $RUNC <<<<<" dnf -y install "$RUNC" + dnf -y upgrade slirp4netns ;& # Continue to the next item centos-7) ;& rhel-7) @@ -88,4 +89,6 @@ then fi fi +show_env_vars + record_timestamp "env. setup end" diff --git a/contrib/spec/podman.spec.in b/contrib/spec/podman.spec.in index 703b942b6..3324ee8f9 100644 --- a/contrib/spec/podman.spec.in +++ b/contrib/spec/podman.spec.in @@ -472,6 +472,7 @@ export GOPATH=%{buildroot}/%{gopath}:$(pwd)/vendor:%{gopath} %{_mandir}/man1/*.1* %{_mandir}/man5/*.5* %{_datadir}/bash-completion/completions/* +%{_datadir}/zsh/site-functions/* %{_libexecdir}/%{name}/conmon %config(noreplace) %{_sysconfdir}/cni/net.d/87-%{name}-bridge.conflist %{_datadir}/containers/%{repo}.conf diff --git a/docs/podman-container-runlabel.1.md b/docs/podman-container-runlabel.1.md index 7547f7187..7fa9805e6 100644 --- a/docs/podman-container-runlabel.1.md +++ b/docs/podman-container-runlabel.1.md @@ -12,6 +12,7 @@ podman-container-runlabel - Execute Image Label Method [**--rootfs**=*ROOTFS*] [**--set**=*NAME*=*VALUE*] [**--storage**] +[**--replace**] LABEL IMAGE [ARG...] # DESCRIPTION @@ -85,6 +86,11 @@ Print usage statement Suppress output information when pulling images +**--replace** + +If a container exists of the default or given name, as needed it will be stopped, deleted and a new container will be +created from this image. + **--signature-policy="PATHNAME"** Pathname of a signature policy file to use. It is not recommended that this diff --git a/docs/podman-image.1.md b/docs/podman-image.1.md index 54960045f..333a75b69 100644 --- a/docs/podman-image.1.md +++ b/docs/podman-image.1.md @@ -27,8 +27,8 @@ The image command allows you to manage images | save | [podman-save(1)](podman-save.1.md) | Save an image to docker-archive or oci. | | sign | [podman-image-sign(1)](podman-image-sign.1.md) | Sign an image. | | tag | [podman-tag(1)](podman-tag.1.md) | Add an additional name to a local image. | +| tree | [podman-image-tree(1)](podman-image-tree.1.md) | Prints layer hierarchy of an image in a tree format. | | trust | [podman-image-trust(1)](podman-image-trust.1.md)| Manage container image trust policy. | -| tree | [podman-image-tree(1)](podman-image-tree.1.md) | Prints layer hierarchy of an image in a tree format | ## SEE ALSO podman diff --git a/docs/podman-load.1.md b/docs/podman-load.1.md index 8b6501a5c..5363f3f1e 100644 --- a/docs/podman-load.1.md +++ b/docs/podman-load.1.md @@ -4,7 +4,7 @@ podman\-load - Load an image from docker archive ## SYNOPSIS -**podman load** *name*[:*tag*|@*digest*] +**podman load** [ARCHIVE] ## DESCRIPTION **podman load** copies an image from either **docker-archive** or **oci-archive** stored diff --git a/docs/podman-logs.1.md b/docs/podman-logs.1.md index bc02df954..8cd6ad5e7 100644 --- a/docs/podman-logs.1.md +++ b/docs/podman-logs.1.md @@ -1,13 +1,13 @@ % podman-logs(1) ## NAME -podman\-logs - Fetch the logs of a container +podman\-logs - Fetch the logs of one or more containers ## SYNOPSIS -**podman** **logs** [*options*] *container* +**podman** **logs** [*options*] *container* [*container...*] ## DESCRIPTION -The podman logs command batch-retrieves whatever logs are present for a container at the time of execution. +The podman logs command batch-retrieves whatever logs are present for one or more containers at the time of execution. This does not guarantee execution order when combined with podman run (i.e. your run may not have generated any logs at the time you execute podman logs diff --git a/docs/podman-run.1.md b/docs/podman-run.1.md index fe98e43ca..cf385717e 100644 --- a/docs/podman-run.1.md +++ b/docs/podman-run.1.md @@ -1129,6 +1129,15 @@ KillMode=process WantedBy=multi-user.target ``` +### Configuring Storage Options from the command line + +Podman allows for the configuration of storage by changing the values +in the /etc/container/storage.conf or by using global options. This +shows how to setup and use fuse-overlayfs for a one time run of busybox +using global options. + +podman --log-level=debug --storage-driver overlay --storage-opt "overlay.mount_program=/usr/bin/fuse-overlayfs" run busybox /bin/sh + ### Rootless Containers Podman runs as a non root user on most systems. This feature requires that a new enough version of shadow-utils diff --git a/libpod/container_internal.go b/libpod/container_internal.go index bea7acd69..ac2d65342 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -337,11 +337,13 @@ func (c *Container) setupStorage(ctx context.Context) error { } // Set the default Entrypoint and Command - if c.config.Entrypoint == nil { - c.config.Entrypoint = containerInfo.Config.Config.Entrypoint - } - if c.config.Command == nil { - c.config.Command = containerInfo.Config.Config.Cmd + if containerInfo.Config != nil { + if c.config.Entrypoint == nil { + c.config.Entrypoint = containerInfo.Config.Config.Entrypoint + } + if c.config.Command == nil { + c.config.Command = containerInfo.Config.Config.Cmd + } } artifacts := filepath.Join(c.config.StaticDir, artifactsDir) @@ -1427,5 +1429,9 @@ func (c *Container) copyWithTarFromImage(src, dest string) error { } a := archive.NewDefaultArchiver() source := filepath.Join(mountpoint, src) + + if err = c.copyOwnerAndPerms(source, dest); err != nil { + return err + } return a.CopyWithTar(source, dest) } diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go index a7b4aed9f..2a7808bdf 100644 --- a/libpod/container_internal_linux.go +++ b/libpod/container_internal_linux.go @@ -982,3 +982,20 @@ func (c *Container) generatePasswd() (string, error) { } return passwdFile, nil } + +func (c *Container) copyOwnerAndPerms(source, dest string) error { + info, err := os.Stat(source) + if err != nil { + if os.IsNotExist(err) { + return nil + } + return errors.Wrapf(err, "cannot stat `%s`", dest) + } + if err := os.Chmod(dest, info.Mode()); err != nil { + return errors.Wrapf(err, "cannot chmod `%s`", dest) + } + if err := os.Chown(dest, int(info.Sys().(*syscall.Stat_t).Uid), int(info.Sys().(*syscall.Stat_t).Gid)); err != nil { + return errors.Wrapf(err, "cannot chown `%s`", dest) + } + return nil +} diff --git a/libpod/container_internal_unsupported.go b/libpod/container_internal_unsupported.go index 4af0cd56c..f707b350c 100644 --- a/libpod/container_internal_unsupported.go +++ b/libpod/container_internal_unsupported.go @@ -35,3 +35,7 @@ func (c *Container) checkpoint(ctx context.Context, options ContainerCheckpointO func (c *Container) restore(ctx context.Context, options ContainerCheckpointOptions) error { return ErrNotImplemented } + +func (c *Container) copyOwnerAndPerms(source, dest string) error { + return nil +} diff --git a/libpod/container_log.go b/libpod/container_log.go new file mode 100644 index 000000000..7964e4022 --- /dev/null +++ b/libpod/container_log.go @@ -0,0 +1,208 @@ +package libpod + +import ( + "fmt" + "io/ioutil" + "strings" + "sync" + "time" + + "github.com/hpcloud/tail" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +const ( + // logTimeFormat is the time format used in the log. + // It is a modified version of RFC3339Nano that guarantees trailing + // zeroes are not trimmed, taken from + // https://github.com/golang/go/issues/19635 + logTimeFormat = "2006-01-02T15:04:05.000000000Z07:00" +) + +// LogOptions is the options you can use for logs +type LogOptions struct { + Details bool + Follow bool + Since time.Time + Tail uint64 + Timestamps bool + Multi bool + WaitGroup *sync.WaitGroup +} + +// LogLine describes the information for each line of a log +type LogLine struct { + Device string + ParseLogType string + Time time.Time + Msg string + CID string +} + +// Log is a runtime function that can read one or more container logs. +func (r *Runtime) Log(containers []*Container, options *LogOptions, logChannel chan *LogLine) error { + for _, ctr := range containers { + if err := ctr.ReadLog(options, logChannel); err != nil { + return err + } + } + return nil +} + +// ReadLog reads a containers log based on the input options and returns loglines over a channel +func (c *Container) ReadLog(options *LogOptions, logChannel chan *LogLine) error { + t, tailLog, err := getLogFile(c.LogPath(), options) + if err != nil { + return errors.Wrapf(err, "unable to read log file %s for %s ", c.ID(), c.LogPath()) + } + options.WaitGroup.Add(1) + if len(tailLog) > 0 { + for _, nll := range tailLog { + nll.CID = c.ID() + if nll.Since(options.Since) { + logChannel <- nll + } + } + } + + go func() { + var partial string + for line := range t.Lines { + nll, err := newLogLine(line.Text) + if err != nil { + logrus.Error(err) + continue + } + if nll.Partial() { + partial = partial + nll.Msg + continue + } else if !nll.Partial() && len(partial) > 1 { + nll.Msg = partial + partial = "" + } + nll.CID = c.ID() + if nll.Since(options.Since) { + logChannel <- nll + } + } + options.WaitGroup.Done() + }() + return nil +} + +// getLogFile returns an hp tail for a container given options +func getLogFile(path string, options *LogOptions) (*tail.Tail, []*LogLine, error) { + var ( + whence int + err error + logTail []*LogLine + ) + // whence 0=origin, 2=end + if options.Tail > 0 { + whence = 2 + logTail, err = getTailLog(path, int(options.Tail)) + if err != nil { + return nil, nil, err + } + } + seek := tail.SeekInfo{ + Offset: 0, + Whence: whence, + } + + t, err := tail.TailFile(path, tail.Config{Poll: true, Follow: options.Follow, Location: &seek, Logger: tail.DiscardingLogger}) + return t, logTail, err +} + +func getTailLog(path string, tail int) ([]*LogLine, error) { + var ( + tailLog []*LogLine + nlls []*LogLine + tailCounter int + partial string + ) + content, err := ioutil.ReadFile(path) + if err != nil { + return nil, err + } + splitContent := strings.Split(string(content), "\n") + // We read the content in reverse and add each nll until we have the same + // number of F type messages as the desired tail + for i := len(splitContent) - 1; i >= 0; i-- { + if len(splitContent[i]) == 0 { + continue + } + nll, err := newLogLine(splitContent[i]) + if err != nil { + return nil, err + } + nlls = append(nlls, nll) + if !nll.Partial() { + tailCounter = tailCounter + 1 + } + if tailCounter == tail { + break + } + } + // Now we iterate the results and assemble partial messages to become full messages + for _, nll := range nlls { + if nll.Partial() { + partial = partial + nll.Msg + } else { + nll.Msg = nll.Msg + partial + tailLog = append(tailLog, nll) + partial = "" + } + } + return tailLog, nil +} + +// String converts a logline to a string for output given whether a detail +// bool is specified. +func (l *LogLine) String(options *LogOptions) string { + var out string + if options.Multi { + cid := l.CID + if len(cid) > 12 { + cid = cid[:12] + } + out = fmt.Sprintf("%s ", cid) + } + if options.Timestamps { + out = out + fmt.Sprintf("%s ", l.Time.Format(logTimeFormat)) + } + return out + l.Msg +} + +// Since returns a bool as to whether a log line occurred after a given time +func (l *LogLine) Since(since time.Time) bool { + return l.Time.After(since) +} + +// newLogLine creates a logLine struct from a container log string +func newLogLine(line string) (*LogLine, error) { + splitLine := strings.Split(line, " ") + if len(splitLine) < 4 { + return nil, errors.Errorf("'%s' is not a valid container log line", line) + } + logTime, err := time.Parse(time.RFC3339Nano, splitLine[0]) + if err != nil { + return nil, errors.Wrapf(err, "unable to convert time %s from container log", splitLine[0]) + } + l := LogLine{ + Time: logTime, + Device: splitLine[1], + ParseLogType: splitLine[2], + Msg: strings.Join(splitLine[3:], " "), + } + return &l, nil +} + +// Partial returns a bool if the log line is a partial log type +func (l *LogLine) Partial() bool { + if l.ParseLogType == "P" { + return true + } + return false +} diff --git a/libpod/events.go b/libpod/events.go index 879aeb6c5..139600982 100644 --- a/libpod/events.go +++ b/libpod/events.go @@ -1,6 +1,8 @@ package libpod import ( + "os" + "github.com/containers/libpod/libpod/events" "github.com/hpcloud/tail" "github.com/pkg/errors" @@ -85,10 +87,10 @@ func (r *Runtime) Events(fromStart, stream bool, options []events.EventFilter, e func (r *Runtime) getTail(fromStart, stream bool) (*tail.Tail, error) { reopen := true - seek := tail.SeekInfo{Offset: 0, Whence: 2} + seek := tail.SeekInfo{Offset: 0, Whence: os.SEEK_END} if fromStart || !stream { seek.Whence = 0 reopen = false } - return tail.TailFile(r.config.EventsLogFilePath, tail.Config{ReOpen: reopen, Follow: stream, Location: &seek}) + return tail.TailFile(r.config.EventsLogFilePath, tail.Config{ReOpen: reopen, Follow: stream, Location: &seek, Logger: tail.DiscardingLogger}) } diff --git a/libpod/networking_linux.go b/libpod/networking_linux.go index d8b0cffcb..2450bd6b1 100644 --- a/libpod/networking_linux.go +++ b/libpod/networking_linux.go @@ -215,9 +215,12 @@ func (r *Runtime) setupRootlessNetNS(ctr *Container) (err error) { if pid != cmd.Process.Pid { continue } - if status.Exited() || status.Signaled() { + if status.Exited() { return errors.New("slirp4netns failed") } + if status.Signaled() { + return errors.New("slirp4netns killed by signal") + } continue } return errors.Wrapf(err, "failed to read from slirp4netns sync pipe") diff --git a/libpod/oci.go b/libpod/oci.go index 30360d289..69cff6d3c 100644 --- a/libpod/oci.go +++ b/libpod/oci.go @@ -183,6 +183,7 @@ func waitPidsStop(pids []int, timeout time.Duration) error { func bindPorts(ports []ocicni.PortMapping) ([]*os.File, error) { var files []*os.File + notifySCTP := false for _, i := range ports { switch i.Protocol { case "udp": @@ -218,6 +219,12 @@ func bindPorts(ports []ocicni.PortMapping) ([]*os.File, error) { } files = append(files, f) break + case "sctp": + if !notifySCTP { + notifySCTP = true + logrus.Warnf("port reservation for SCTP is not supported") + } + break default: return nil, fmt.Errorf("unknown protocol %s", i.Protocol) diff --git a/libpod/runtime.go b/libpod/runtime.go index fa208a2ca..9836b7aab 100644 --- a/libpod/runtime.go +++ b/libpod/runtime.go @@ -241,6 +241,12 @@ type runtimeConfiguredFrom struct { libpodStaticDirSet bool libpodTmpDirSet bool volPathSet bool + conmonPath bool + conmonEnvVars bool + ociRuntimes bool + runtimePath bool + cniPluginDir bool + noPivotRoot bool } var ( @@ -324,6 +330,22 @@ func SetXdgRuntimeDir(val string) error { // NewRuntime creates a new container runtime // Options can be passed to override the default configuration for the runtime func NewRuntime(options ...RuntimeOption) (runtime *Runtime, err error) { + return newRuntimeFromConfig("", options...) +} + +// NewRuntimeFromConfig creates a new container runtime using the given +// configuration file for its default configuration. Passed RuntimeOption +// functions can be used to mutate this configuration further. +// An error will be returned if the configuration file at the given path does +// not exist or cannot be loaded +func NewRuntimeFromConfig(userConfigPath string, options ...RuntimeOption) (runtime *Runtime, err error) { + if userConfigPath == "" { + return nil, errors.New("invalid configuration file specified") + } + return newRuntimeFromConfig(userConfigPath, options...) +} + +func newRuntimeFromConfig(userConfigPath string, options ...RuntimeOption) (runtime *Runtime, err error) { runtime = new(Runtime) runtime.config = new(RuntimeConfig) runtime.configuredFrom = new(runtimeConfiguredFrom) @@ -358,11 +380,6 @@ func NewRuntime(options ...RuntimeOption) (runtime *Runtime, err error) { rootlessConfigPath = filepath.Join(home, ".config/containers/libpod.conf") - configPath = rootlessConfigPath - if _, err := os.Stat(configPath); err != nil { - foundConfig = false - } - runtimeDir, err := util.GetRootlessRuntimeDir() if err != nil { return nil, err @@ -374,6 +391,20 @@ func NewRuntime(options ...RuntimeOption) (runtime *Runtime, err error) { return nil, errors.Wrapf(err, "cannot set XDG_RUNTIME_DIR") } + } + + if userConfigPath != "" { + configPath = userConfigPath + if _, err := os.Stat(configPath); err != nil { + // If the user specified a config file, we must fail immediately + // when it doesn't exist + return nil, errors.Wrapf(err, "cannot stat %s", configPath) + } + } else if rootless.IsRootless() { + configPath = rootlessConfigPath + if _, err := os.Stat(configPath); err != nil { + foundConfig = false + } } else if _, err := os.Stat(OverrideConfigPath); err == nil { // Use the override configuration path configPath = OverrideConfigPath @@ -409,6 +440,24 @@ func NewRuntime(options ...RuntimeOption) (runtime *Runtime, err error) { if tmpConfig.VolumePath != "" { runtime.configuredFrom.volPathSet = true } + if tmpConfig.ConmonPath != nil { + runtime.configuredFrom.conmonPath = true + } + if tmpConfig.ConmonEnvVars != nil { + runtime.configuredFrom.conmonEnvVars = true + } + if tmpConfig.OCIRuntimes != nil { + runtime.configuredFrom.ociRuntimes = true + } + if tmpConfig.RuntimePath != nil { + runtime.configuredFrom.runtimePath = true + } + if tmpConfig.CNIPluginDir != nil { + runtime.configuredFrom.cniPluginDir = true + } + if tmpConfig.NoPivotRoot { + runtime.configuredFrom.noPivotRoot = true + } if _, err := toml.Decode(string(contents), runtime.config); err != nil { return nil, errors.Wrapf(err, "error decoding configuration file %s", configPath) @@ -428,12 +477,24 @@ func NewRuntime(options ...RuntimeOption) (runtime *Runtime, err error) { } // Cherry pick the settings we want from the global configuration - runtime.config.ConmonPath = tmpConfig.ConmonPath - runtime.config.ConmonEnvVars = tmpConfig.ConmonEnvVars - runtime.config.OCIRuntimes = tmpConfig.OCIRuntimes - runtime.config.RuntimePath = tmpConfig.RuntimePath - runtime.config.CNIPluginDir = tmpConfig.CNIPluginDir - runtime.config.NoPivotRoot = tmpConfig.NoPivotRoot + if !runtime.configuredFrom.conmonPath { + runtime.config.ConmonPath = tmpConfig.ConmonPath + } + if !runtime.configuredFrom.conmonEnvVars { + runtime.config.ConmonEnvVars = tmpConfig.ConmonEnvVars + } + if !runtime.configuredFrom.ociRuntimes { + runtime.config.OCIRuntimes = tmpConfig.OCIRuntimes + } + if !runtime.configuredFrom.runtimePath { + runtime.config.RuntimePath = tmpConfig.RuntimePath + } + if !runtime.configuredFrom.cniPluginDir { + runtime.config.CNIPluginDir = tmpConfig.CNIPluginDir + } + if !runtime.configuredFrom.noPivotRoot { + runtime.config.NoPivotRoot = tmpConfig.NoPivotRoot + } break } } @@ -465,80 +526,9 @@ func NewRuntime(options ...RuntimeOption) (runtime *Runtime, err error) { return runtime, nil } -// NewRuntimeFromConfig creates a new container runtime using the given -// configuration file for its default configuration. Passed RuntimeOption -// functions can be used to mutate this configuration further. -// An error will be returned if the configuration file at the given path does -// not exist or cannot be loaded -func NewRuntimeFromConfig(configPath string, options ...RuntimeOption) (runtime *Runtime, err error) { - runtime = new(Runtime) - runtime.config = new(RuntimeConfig) - runtime.configuredFrom = new(runtimeConfiguredFrom) - - // Set three fields not in the TOML config - runtime.config.StateType = defaultRuntimeConfig.StateType - runtime.config.OCIRuntime = defaultRuntimeConfig.OCIRuntime - - storageConf, err := util.GetDefaultStoreOptions() - if err != nil { - return nil, errors.Wrapf(err, "error retrieving storage config") - } - runtime.config.StorageConfig = storageConf - runtime.config.StaticDir = filepath.Join(storageConf.GraphRoot, "libpod") - runtime.config.VolumePath = filepath.Join(storageConf.GraphRoot, "volumes") - - tmpDir, err := getDefaultTmpDir() - if err != nil { - return nil, err - } - runtime.config.TmpDir = tmpDir - if rootless.IsRootless() { - runtimeDir, err := util.GetRootlessRuntimeDir() - if err != nil { - return nil, err - } - // containers/image uses XDG_RUNTIME_DIR to locate the auth file. - // So make sure the env variable is set. - if err := SetXdgRuntimeDir(runtimeDir); err != nil { - return nil, errors.Wrapf(err, "cannot set XDG_RUNTIME_DIR") - } - } - - // Check to see if the given configuration file exists - if _, err := os.Stat(configPath); err != nil { - return nil, errors.Wrapf(err, "error checking existence of configuration file %s", configPath) - } - - // Read contents of the config file - contents, err := ioutil.ReadFile(configPath) - if err != nil { - return nil, errors.Wrapf(err, "error reading configuration file %s", configPath) - } - - // Decode configuration file - if _, err := toml.Decode(string(contents), runtime.config); err != nil { - return nil, errors.Wrapf(err, "error decoding configuration from file %s", configPath) - } - - // Overwrite the config with user-given configuration options - for _, opt := range options { - if err := opt(runtime); err != nil { - return nil, errors.Wrapf(err, "error configuring runtime") - } - } - - if err := makeRuntime(runtime); err != nil { - return nil, err - } - - return runtime, nil -} - // Make a new runtime based on the given configuration // Sets up containers/storage, state store, OCI runtime func makeRuntime(runtime *Runtime) (err error) { - runtime.config.EventsLogFilePath = filepath.Join(runtime.config.TmpDir, "events", "events.log") - // Backward compatibility for `runtime_path` if runtime.config.RuntimePath != nil { // Don't print twice in rootless mode. @@ -697,6 +687,8 @@ func makeRuntime(runtime *Runtime) (err error) { runtime.config.VolumePath = dbConfig.VolumePath } + runtime.config.EventsLogFilePath = filepath.Join(runtime.config.TmpDir, "events", "events.log") + logrus.Debugf("Using graph driver %s", runtime.config.StorageConfig.GraphDriverName) logrus.Debugf("Using graph root %s", runtime.config.StorageConfig.GraphRoot) logrus.Debugf("Using run root %s", runtime.config.StorageConfig.RunRoot) diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go index c6f119913..3b74a65dd 100644 --- a/libpod/runtime_ctr.go +++ b/libpod/runtime_ctr.go @@ -171,7 +171,7 @@ func (r *Runtime) newContainer(ctx context.Context, rSpec *spec.Spec, options .. }() if rootless.IsRootless() && ctr.config.ConmonPidFile == "" { - ctr.config.ConmonPidFile = filepath.Join(ctr.state.RunDir, "conmon.pid") + ctr.config.ConmonPidFile = filepath.Join(ctr.config.StaticDir, "conmon.pid") } // Go through the volume mounts and check for named volumes diff --git a/pkg/adapter/containers.go b/pkg/adapter/containers.go index 756369196..932d209cd 100644 --- a/pkg/adapter/containers.go +++ b/pkg/adapter/containers.go @@ -4,7 +4,9 @@ package adapter import ( "context" + "fmt" "strconv" + "sync" "syscall" "time" @@ -127,3 +129,28 @@ func (r *LocalRuntime) WaitOnContainers(ctx context.Context, cli *cliconfig.Wait } return ok, failures, err } + +// Log logs one or more containers +func (r *LocalRuntime) Log(c *cliconfig.LogsValues, options *libpod.LogOptions) error { + var wg sync.WaitGroup + options.WaitGroup = &wg + if len(c.InputArgs) > 1 { + options.Multi = true + } + logChannel := make(chan *libpod.LogLine, int(c.Tail)*len(c.InputArgs)+1) + containers, err := shortcuts.GetContainersByContext(false, c.Latest, c.InputArgs, r.Runtime) + if err != nil { + return err + } + if err := r.Runtime.Log(containers, options, logChannel); err != nil { + return err + } + go func() { + wg.Wait() + close(logChannel) + }() + for line := range logChannel { + fmt.Println(line.String(options)) + } + return nil +} diff --git a/pkg/adapter/containers_remote.go b/pkg/adapter/containers_remote.go index 5646d2297..a8146567a 100644 --- a/pkg/adapter/containers_remote.go +++ b/pkg/adapter/containers_remote.go @@ -5,18 +5,19 @@ package adapter import ( "context" "encoding/json" - "errors" + "fmt" "strconv" "syscall" "time" "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/shared" - "github.com/sirupsen/logrus" - - iopodman "github.com/containers/libpod/cmd/podman/varlink" + "github.com/containers/libpod/cmd/podman/varlink" "github.com/containers/libpod/libpod" "github.com/containers/libpod/pkg/inspect" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "github.com/varlink/go/varlink" ) // Inspect returns an inspect struct from varlink @@ -223,3 +224,41 @@ func BatchContainerOp(ctr *Container, opts shared.PsOptions) (shared.BatchContai } return bcs, nil } + +// Logs one or more containers over a varlink connection +func (r *LocalRuntime) Log(c *cliconfig.LogsValues, options *libpod.LogOptions) error { + //GetContainersLogs + reply, err := iopodman.GetContainersLogs().Send(r.Conn, uint64(varlink.More), c.InputArgs, c.Follow, c.Latest, options.Since.Format(time.RFC3339Nano), int64(c.Tail), c.Timestamps) + if err != nil { + return errors.Wrapf(err, "failed to get container logs") + } + if len(c.InputArgs) > 1 { + options.Multi = true + } + for { + log, flags, err := reply() + if err != nil { + return err + } + if log.Time == "" && log.Msg == "" { + // We got a blank log line which can signal end of stream + break + } + lTime, err := time.Parse(time.RFC3339Nano, log.Time) + if err != nil { + return errors.Wrapf(err, "unable to parse time of log %s", log.Time) + } + logLine := libpod.LogLine{ + Device: log.Device, + ParseLogType: log.ParseLogType, + Time: lTime, + Msg: log.Msg, + CID: log.Cid, + } + fmt.Println(logLine.String(options)) + if flags&varlink.Continues == 0 { + break + } + } + return nil +} diff --git a/pkg/rootless/rootless_linux.go b/pkg/rootless/rootless_linux.go index b2677f7d9..baceebee3 100644 --- a/pkg/rootless/rootless_linux.go +++ b/pkg/rootless/rootless_linux.go @@ -72,7 +72,7 @@ func GetRootlessUID() int { u, _ := strconv.Atoi(uidEnv) return u } - return os.Getuid() + return os.Geteuid() } func tryMappingTool(tool string, pid int, hostID int, mappings []idtools.IDMap) error { diff --git a/pkg/util/utils.go b/pkg/util/utils.go index d7e1ddd38..73dddf2ac 100644 --- a/pkg/util/utils.go +++ b/pkg/util/utils.go @@ -190,15 +190,15 @@ func GetRootlessRuntimeDir() (string, error) { tmpDir := filepath.Join("/run", "user", uid) os.MkdirAll(tmpDir, 0700) st, err := os.Stat(tmpDir) - if err == nil && int(st.Sys().(*syscall.Stat_t).Uid) == os.Getuid() && st.Mode().Perm() == 0700 { + if err == nil && int(st.Sys().(*syscall.Stat_t).Uid) == os.Geteuid() && st.Mode().Perm() == 0700 { runtimeDir = tmpDir } } if runtimeDir == "" { - tmpDir := filepath.Join(os.TempDir(), "user", uid) + tmpDir := filepath.Join(os.TempDir(), fmt.Sprintf("libpod-rundir-%s", uid)) os.MkdirAll(tmpDir, 0700) st, err := os.Stat(tmpDir) - if err == nil && int(st.Sys().(*syscall.Stat_t).Uid) == os.Getuid() && st.Mode().Perm() == 0700 { + if err == nil && int(st.Sys().(*syscall.Stat_t).Uid) == os.Geteuid() && st.Mode().Perm() == 0700 { runtimeDir = tmpDir } } diff --git a/pkg/varlinkapi/containers.go b/pkg/varlinkapi/containers.go index fe38a7cdc..3185ba0e9 100644 --- a/pkg/varlinkapi/containers.go +++ b/pkg/varlinkapi/containers.go @@ -7,6 +7,7 @@ import ( "io" "io/ioutil" "os" + "sync" "syscall" "time" @@ -602,3 +603,56 @@ func ContainerStatsToLibpodContainerStats(stats iopodman.ContainerStats) libpod. } return cstats } + +// GetContainersLogs is the varlink endpoint to obtain one or more container logs +func (i *LibpodAPI) GetContainersLogs(call iopodman.VarlinkCall, names []string, follow, latest bool, since string, tail int64, timestamps bool) error { + var wg sync.WaitGroup + if call.WantsMore() { + call.Continues = true + } + sinceTime, err := time.Parse(time.RFC3339Nano, since) + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + options := libpod.LogOptions{ + Follow: follow, + Since: sinceTime, + Tail: uint64(tail), + Timestamps: timestamps, + } + + options.WaitGroup = &wg + if len(names) > 1 { + options.Multi = true + } + logChannel := make(chan *libpod.LogLine, int(tail)*len(names)+1) + containers, err := shortcuts.GetContainersByContext(false, latest, names, i.Runtime) + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + if err := i.Runtime.Log(containers, &options, logChannel); err != nil { + return err + } + go func() { + wg.Wait() + close(logChannel) + }() + for line := range logChannel { + call.ReplyGetContainersLogs(newPodmanLogLine(line)) + if !call.Continues { + break + } + + } + return call.ReplyGetContainersLogs(iopodman.LogLine{}) +} + +func newPodmanLogLine(line *libpod.LogLine) iopodman.LogLine { + return iopodman.LogLine{ + Device: line.Device, + ParseLogType: line.ParseLogType, + Time: line.Time.Format(time.RFC3339Nano), + Msg: line.Msg, + Cid: line.CID, + } +} diff --git a/test/e2e/info_test.go b/test/e2e/info_test.go index 046297bc0..c960fb311 100644 --- a/test/e2e/info_test.go +++ b/test/e2e/info_test.go @@ -41,7 +41,7 @@ var _ = Describe("Podman Info", func() { }) It("podman system info json output", func() { session := podmanTest.Podman([]string{"system", "info", "--format=json"}) - session.Wait() + session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) }) diff --git a/test/e2e/logs_test.go b/test/e2e/logs_test.go index b7d959de9..d383a83b3 100644 --- a/test/e2e/logs_test.go +++ b/test/e2e/logs_test.go @@ -4,6 +4,7 @@ package integration import ( "os" + "strings" . "github.com/containers/libpod/test/utils" . "github.com/onsi/ginkgo" @@ -34,7 +35,6 @@ var _ = Describe("Podman logs", func() { }) - //sudo bin/podman run -it --rm fedora-minimal bash -c 'for a in `seq 5`; do echo hello; done' It("podman logs for container", func() { logc := podmanTest.Podman([]string{"run", "-dt", ALPINE, "sh", "-c", "echo podman; echo podman; echo podman"}) logc.WaitWithDefaultTimeout() @@ -106,4 +106,30 @@ var _ = Describe("Podman logs", func() { Expect(results.ExitCode()).To(Equal(0)) Expect(len(results.OutputToStringArray())).To(Equal(3)) }) + + It("podman logs latest and container name should fail", func() { + results := podmanTest.Podman([]string{"logs", "-l", "foobar"}) + results.WaitWithDefaultTimeout() + Expect(results.ExitCode()).ToNot(Equal(0)) + }) + + It("podman logs two containers and should display short container IDs", func() { + log1 := podmanTest.Podman([]string{"run", "-dt", ALPINE, "sh", "-c", "echo podman; echo podman; echo podman"}) + log1.WaitWithDefaultTimeout() + Expect(log1.ExitCode()).To(Equal(0)) + cid1 := log1.OutputToString() + + log2 := podmanTest.Podman([]string{"run", "-dt", ALPINE, "sh", "-c", "echo podman; echo podman; echo podman"}) + log2.WaitWithDefaultTimeout() + Expect(log2.ExitCode()).To(Equal(0)) + cid2 := log2.OutputToString() + + results := podmanTest.Podman([]string{"logs", cid1, cid2}) + results.WaitWithDefaultTimeout() + Expect(results.ExitCode()).To(Equal(0)) + + output := results.OutputToStringArray() + Expect(len(output)).To(Equal(6)) + Expect(strings.Contains(output[0], cid1[:12]) || strings.Contains(output[0], cid2[:12])).To(BeTrue()) + }) }) diff --git a/test/e2e/ps_test.go b/test/e2e/ps_test.go index 58697acde..92ca538f0 100644 --- a/test/e2e/ps_test.go +++ b/test/e2e/ps_test.go @@ -303,4 +303,18 @@ var _ = Describe("Podman ps", func() { Expect(session.OutputToString()).To(ContainSubstring(podid)) }) + + It("podman ps test with port range", func() { + session := podmanTest.RunTopContainer("") + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.Podman([]string{"run", "-dt", "-p", "1000-1006:1000-1006", ALPINE, "top"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.Podman([]string{"ps", "--format", "{{.Ports}}"}) + session.WaitWithDefaultTimeout() + Expect(session.OutputToString()).To(ContainSubstring("0.0.0.0:1000-1006")) + }) }) diff --git a/test/e2e/rootless_test.go b/test/e2e/rootless_test.go index cd771e2ba..57146bca0 100644 --- a/test/e2e/rootless_test.go +++ b/test/e2e/rootless_test.go @@ -138,6 +138,21 @@ var _ = Describe("Podman rootless", func() { cmd.WaitWithDefaultTimeout() Expect(cmd.ExitCode()).To(Equal(0)) Expect(cmd.LineInOutputContains("hello")).To(BeTrue()) + + args = []string{"pod", "top", podId} + cmd = rootlessTest.PodmanAsUser(args, 1000, 1000, "", env) + cmd.WaitWithDefaultTimeout() + Expect(cmd.ExitCode()).To(Not(Equal(0))) + + args = []string{"run", "--pod", podId, "-d", "--rootfs", mountPath, "sleep", "100"} + cmd = rootlessTest.PodmanAsUser(args, 1000, 1000, "", env) + cmd.WaitWithDefaultTimeout() + Expect(cmd.ExitCode()).To(Equal(0)) + + args = []string{"pod", "top", podId} + cmd = rootlessTest.PodmanAsUser(args, 1000, 1000, "", env) + cmd.WaitWithDefaultTimeout() + Expect(cmd.ExitCode()).To(Equal(0)) } runInRootlessContext(f) }) diff --git a/test/test_podman_build.sh b/test/test_podman_build.sh index 9faefc78a..39f1e784d 100644 --- a/test/test_podman_build.sh +++ b/test/test_podman_build.sh @@ -34,6 +34,13 @@ echo ######################################################## echo ######################################################## +echo test "build directory before other options create a tag" +echo ######################################################## +TARGET=tagged-image +podman build $HOME/test/build/from-scratch --quiet=True -t $TARGET +podman images | grep tagged-image + +echo ######################################################## echo test "build-preserve-subvolumes" echo ######################################################## TARGET=volume-image |