summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.cirrus.yml67
-rw-r--r--README.md2
-rw-r--r--cmd/podman/commands.go2
-rw-r--r--cmd/podman/container.go1
-rw-r--r--cmd/podman/generate_kube.go4
-rw-r--r--cmd/podman/load.go71
-rw-r--r--cmd/podman/logs.go58
-rw-r--r--cmd/podman/main.go2
-rw-r--r--cmd/podman/mount.go2
-rw-r--r--cmd/podman/pod_inspect.go9
-rw-r--r--cmd/podman/pod_stats.go2
-rw-r--r--cmd/podman/pod_top.go26
-rw-r--r--cmd/podman/rm.go5
-rw-r--r--cmd/podman/search.go19
-rw-r--r--cmd/podman/shared/container.go80
-rw-r--r--cmd/podman/tree.go2
-rw-r--r--cmd/podman/umount.go2
-rw-r--r--cmd/podman/varlink/io.podman.varlink10
-rw-r--r--commands.md2
-rw-r--r--contrib/cirrus/lib.sh13
-rwxr-xr-xcontrib/cirrus/notice_master_failure.sh19
-rwxr-xr-xcontrib/cirrus/setup_environment.sh3
-rw-r--r--docs/podman-image.1.md2
-rw-r--r--docs/podman-load.1.md18
-rw-r--r--docs/podman-logs.1.md6
-rw-r--r--docs/podman-ps.1.md9
-rw-r--r--docs/podman-run.1.md9
-rw-r--r--docs/podman-save.1.md5
-rw-r--r--install.md1
-rw-r--r--libpod/container_internal.go4
-rw-r--r--libpod/container_internal_linux.go17
-rw-r--r--libpod/container_internal_unsupported.go4
-rw-r--r--libpod/container_log.go213
-rw-r--r--libpod/events.go2
-rw-r--r--libpod/networking_linux.go5
-rw-r--r--libpod/oci.go7
-rw-r--r--libpod/runtime.go37
-rw-r--r--libpod/runtime_ctr.go2
-rw-r--r--pkg/adapter/containers.go27
-rw-r--r--pkg/adapter/containers_remote.go47
-rw-r--r--pkg/util/utils.go53
-rw-r--r--pkg/varlinkapi/containers.go54
-rw-r--r--test/e2e/logs_test.go38
-rw-r--r--test/e2e/ps_test.go14
-rw-r--r--test/e2e/rm_test.go14
-rw-r--r--test/e2e/rootless_test.go15
46 files changed, 817 insertions, 187 deletions
diff --git a/.cirrus.yml b/.cirrus.yml
index 8e26ce72e..8ac87c1d7 100644
--- a/.cirrus.yml
+++ b/.cirrus.yml
@@ -28,15 +28,15 @@ env:
#### Cache-image names to test with
###
ACTIVE_CACHE_IMAGE_NAMES: >-
- fedora-29-libpod-d6d53e40
- fedora-28-libpod-d6d53e40
- ubuntu-18-libpod-d6d53e40
- rhel-7-libpod-7f4cd1f7
+ fedora-28-libpod-6318419153518592
+ fedora-29-libpod-6318419153518592
+ ubuntu-18-libpod-6318419153518592
+ rhel-7-libpod-6318419153518592
image-builder-image-1541772081
- FEDORA_CACHE_IMAGE_NAME: "fedora-29-libpod-d6d53e40"
- PRIOR_FEDORA_CACHE_IMAGE_NAME: "fedora-28-libpod-d6d53e40"
- UBUNTU_CACHE_IMAGE_NAME: "ubuntu-18-libpod-d6d53e40"
- PRIOR_RHEL_CACHE_IMAGE_NAME: "rhel-7-libpod-7f4cd1f7"
+ FEDORA_CACHE_IMAGE_NAME: "fedora-29-libpod-6318419153518592"
+ PRIOR_FEDORA_CACHE_IMAGE_NAME: "fedora-28-libpod-6318419153518592"
+ UBUNTU_CACHE_IMAGE_NAME: "ubuntu-18-libpod-6318419153518592"
+ PRIOR_RHEL_CACHE_IMAGE_NAME: "rhel-7-libpod-6318419153518592"
# RHEL_CACHE_IMAGE_NAME: "rhel-8-notready"
# CENTOS_CACHE_IMAGE_NAME: "centos-7-notready"
@@ -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
-
- # ...or lists of strings
- unit_test_script:
- - go version
- - $SCRIPT_BASE/unit_test.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'
- 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,16 +339,21 @@ 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:
+ only_if: $CIRRUS_BRANCH != 'master'
+
depends_on: # ignores any dependent task conditions
- "gating"
- - "vendor_check"
+ - "build_each_commit_task"
- "testing"
+ - "rootless_testing_task"
- "optional_testing"
- - "cache_images"
env:
CIRRUS_WORKING_DIR: "/usr/src/libpod"
@@ -350,4 +363,4 @@ success_task:
cpu: 1
memory: 1
- success_script: $SCRIPT_BASE/success.sh
+ success_script: '$SCRIPT_BASE/success.sh'
diff --git a/README.md b/README.md
index 636a6fa5b..fe78c9d58 100644
--- a/README.md
+++ b/README.md
@@ -12,7 +12,7 @@ popularized by Kubernetes. Libpod also contains the Pod Manager tool `(Podman)`
At a high level, the scope of libpod and podman is the following:
-* Support multiple image formats including the existing Docker/OCI image formats.
+* Support multiple image formats including the OCI and Docker image formats.
* Support for multiple means to download images including trust & image verification.
* Container image management (managing image layers, overlay filesystems, etc).
* Full management of container lifecycle
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/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/generate_kube.go b/cmd/podman/generate_kube.go
index e3db14af3..42cfba8d8 100644
--- a/cmd/podman/generate_kube.go
+++ b/cmd/podman/generate_kube.go
@@ -57,8 +57,8 @@ func generateKubeYAMLCmd(c *cliconfig.GenerateKubeValues) error {
return errors.Wrapf(libpod.ErrNotImplemented, "rootless users")
}
args := c.InputArgs
- if len(args) > 1 || (len(args) < 1 && !c.Bool("latest")) {
- return errors.Errorf("you must provide one container|pod ID or name or --latest")
+ if len(args) != 1 {
+ return errors.Errorf("you must provide exactly one container|pod ID or name")
}
runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand)
diff --git a/cmd/podman/load.go b/cmd/podman/load.go
index 46add699e..04ff9fcca 100644
--- a/cmd/podman/load.go
+++ b/cmd/podman/load.go
@@ -5,21 +5,24 @@ import (
"io"
"io/ioutil"
"os"
+ "strings"
"github.com/containers/libpod/cmd/podman/cliconfig"
"github.com/containers/libpod/cmd/podman/shared/parse"
"github.com/containers/libpod/pkg/adapter"
"github.com/pkg/errors"
"github.com/spf13/cobra"
+ "golang.org/x/crypto/ssh/terminal"
)
var (
loadCommand cliconfig.LoadValues
- loadDescription = "Loads the image from docker-archive stored on the local machine."
- _loadCommand = &cobra.Command{
- Use: "load [flags] [PATH]",
- Short: "Load an image from docker archive",
+ loadDescription = "Loads an image from a locally stored archive (tar file) into container storage."
+
+ _loadCommand = &cobra.Command{
+ Use: "load [flags] [NAME[:TAG]]",
+ Short: "Load an image from container archive",
Long: loadDescription,
RunE: func(cmd *cobra.Command, args []string) error {
loadCommand.InputArgs = args
@@ -60,49 +63,43 @@ func loadCmd(c *cliconfig.LoadValues) error {
}
defer runtime.Shutdown(false)
- input := c.Input
- if runtime.Remote && len(input) == 0 {
- return errors.New("the remote client requires you to load via -i and a tarball")
- }
- if len(input) == 0 {
- input = "/dev/stdin"
- c.Input = input
-
- fi, err := os.Stdin.Stat()
- if err != nil {
+ if len(c.Input) > 0 {
+ if err := parse.ValidateFileName(c.Input); err != nil {
return err
}
- // checking if loading from pipe
- if !fi.Mode().IsRegular() {
- outFile, err := ioutil.TempFile("/var/tmp", "podman")
- if err != nil {
- return errors.Errorf("error creating file %v", err)
- }
- defer os.Remove(outFile.Name())
- defer outFile.Close()
-
- inFile, err := os.OpenFile(input, 0, 0666)
- if err != nil {
- return errors.Errorf("error reading file %v", err)
- }
- defer inFile.Close()
-
- _, err = io.Copy(outFile, inFile)
- if err != nil {
- return errors.Errorf("error copying file %v", err)
- }
+ } else {
+ if terminal.IsTerminal(int(os.Stdin.Fd())) {
+ return errors.Errorf("cannot read from terminal. Use command-line redirection or the --input flag.")
+ }
+ outFile, err := ioutil.TempFile("/var/tmp", "podman")
+ if err != nil {
+ return errors.Errorf("error creating file %v", err)
+ }
+ defer os.Remove(outFile.Name())
+ defer outFile.Close()
- input = outFile.Name()
+ _, err = io.Copy(outFile, os.Stdin)
+ if err != nil {
+ return errors.Errorf("error copying file %v", err)
}
- }
- if err := parse.ValidateFileName(input); err != nil {
- return err
+
+ c.Input = outFile.Name()
}
names, err := runtime.LoadImage(getContext(), imageName, c)
if err != nil {
return err
}
+ if len(imageName) > 0 {
+ split := strings.Split(names, ",")
+ newImage, err := runtime.NewImageFromLocal(split[0])
+ if err != nil {
+ return err
+ }
+ if err := newImage.TagImage(imageName); err != nil {
+ return errors.Wrapf(err, "error adding '%s' to image %q", imageName, newImage.InputName)
+ }
+ }
fmt.Println("Loaded image(s): " + names)
return nil
}
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 1717e0624..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,
diff --git a/cmd/podman/mount.go b/cmd/podman/mount.go
index 4381074ab..d074551ce 100644
--- a/cmd/podman/mount.go
+++ b/cmd/podman/mount.go
@@ -25,7 +25,7 @@ var (
`
_mountCommand = &cobra.Command{
- Use: "mount [flags] CONTAINER",
+ Use: "mount [flags] [CONTAINER]",
Short: "Mount a working container's root filesystem",
Long: mountDescription,
RunE: func(cmd *cobra.Command, args []string) error {
diff --git a/cmd/podman/pod_inspect.go b/cmd/podman/pod_inspect.go
index 79ffe2e6f..851f39aa0 100644
--- a/cmd/podman/pod_inspect.go
+++ b/cmd/podman/pod_inspect.go
@@ -14,7 +14,7 @@ var (
podInspectCommand cliconfig.PodInspectValues
podInspectDescription = `Display the configuration for a pod by name or id
- By default, this will render all results in a JSON array. If the container and image have the same name, this command returns the container JSON.`
+ By default, this will render all results in a JSON array.`
_podInspectCommand = &cobra.Command{
Use: "inspect [flags] POD",
@@ -34,7 +34,7 @@ func init() {
podInspectCommand.SetHelpTemplate(HelpTemplate())
podInspectCommand.SetUsageTemplate(UsageTemplate())
flags := podInspectCommand.Flags()
- flags.BoolVarP(&podInspectCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of")
+ flags.BoolVarP(&podInspectCommand.Latest, "latest", "l", false, "Act on the latest pod podman is aware of")
markFlagHiddenForRemoteClient("latest", flags)
}
@@ -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_stats.go b/cmd/podman/pod_stats.go
index 701051938..e8ff322ce 100644
--- a/cmd/podman/pod_stats.go
+++ b/cmd/podman/pod_stats.go
@@ -25,7 +25,7 @@ var (
podStatsDescription = `For each specified pod this command will display percentage of CPU, memory, network I/O, block I/O and PIDs for containers in one the pods.`
_podStatsCommand = &cobra.Command{
- Use: "stats [flags] POD [POD...]",
+ Use: "stats [flags] [POD...]",
Short: "Display a live stream of resource usage statistics for the containers in one or more pods",
Long: podStatsDescription,
RunE: func(cmd *cobra.Command, args []string) error {
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/rm.go b/cmd/podman/rm.go
index 56aaae9eb..299420bb6 100644
--- a/cmd/podman/rm.go
+++ b/cmd/podman/rm.go
@@ -195,5 +195,10 @@ func rmCmd(c *cliconfig.RmValues) error {
exitCode = 1
}
}
+
+ if failureCnt > 0 {
+ exitCode = 125
+ }
+
return err
}
diff --git a/cmd/podman/search.go b/cmd/podman/search.go
index 5997e144a..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"
@@ -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/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/umount.go b/cmd/podman/umount.go
index c57d5794c..a938c7c38 100644
--- a/cmd/podman/umount.go
+++ b/cmd/podman/umount.go
@@ -31,7 +31,7 @@ var (
return umountCmd(&umountCommand)
},
Args: func(cmd *cobra.Command, args []string) error {
- return checkAllAndLatest(cmd, args, true)
+ return checkAllAndLatest(cmd, args, false)
},
Example: `podman umount ctrID
podman umount ctrID1 ctrID2 ctrID3
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/commands.md b/commands.md
index 6c5fad2f6..31a77c0c4 100644
--- a/commands.md
+++ b/commands.md
@@ -34,7 +34,7 @@
| [podman-info(1)](/docs/podman-info.1.md) | Display system information |[![...](/docs/play.png)](https://asciinema.org/a/yKbi5fQ89y5TJ8e1RfJd4ivTD)|
| [podman-inspect(1)](/docs/podman-inspect.1.md) | Display the configuration of a container or image |[![...](/docs/play.png)](https://asciinema.org/a/133418)|
| [podman-kill(1)](/docs/podman-kill.1.md) | Kill the main process in one or more running containers |[![...](/docs/play.png)](https://asciinema.org/a/3jNos0A5yzO4hChu7ddKkUPw7)|
-| [podman-load(1)](/docs/podman-load.1.md) | Load an image from docker archive or oci |[![...](/docs/play.png)](https://asciinema.org/a/kp8kOaexEhEa20P1KLZ3L5X4g)|
+| [podman-load(1)](/docs/podman-load.1.md) | Load an image from a container image archive |[![...](/docs/play.png)](https://asciinema.org/a/kp8kOaexEhEa20P1KLZ3L5X4g)|
| [podman-login(1)](/docs/podman-login.1.md) | Login to a container registry |[![...](/docs/play.png)](https://asciinema.org/a/oNiPgmfo1FjV2YdesiLpvihtV)|
| [podman-logout(1)](/docs/podman-logout.1.md) | Logout of a container registry |[![...](/docs/play.png)](https://asciinema.org/a/oNiPgmfo1FjV2YdesiLpvihtV)|
| [podman-logs(1)](/docs/podman-logs.1.md) | Display the logs of a container |[![...](/docs/play.png)](https://asciinema.org/a/MZPTWD5CVs3dMREkBxQBY9C5z)|
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 ead2f7343..04c19b3af 100755
--- a/contrib/cirrus/setup_environment.sh
+++ b/contrib/cirrus/setup_environment.sh
@@ -66,7 +66,6 @@ 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)
@@ -89,4 +88,6 @@ then
fi
fi
+show_env_vars
+
record_timestamp "env. setup end"
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..8a1660c63 100644
--- a/docs/podman-load.1.md
+++ b/docs/podman-load.1.md
@@ -1,22 +1,24 @@
% podman-load(1)
## NAME
-podman\-load - Load an image from docker archive
+podman\-load - Load an image from a container image archive into container storage
## SYNOPSIS
-**podman load** *name*[:*tag*|@*digest*]
+**podman load** [*name*[:*tag*]]
## DESCRIPTION
-**podman load** copies an image from either **docker-archive** or **oci-archive** stored
-on the local machine. **podman load** reads from stdin by default or a file if the **input** flag is set.
-The **quiet** flag suppresses the output when set.
+**podman load** loads an image from either an **oci-archive** or **docker-archive** stored on the local machine into container storage. **podman load** reads from stdin by default or a file if the **input** option is set.
+You can also specify a name for the image if the archive does not contain a named reference, of if you want an additonal name for the local image.
+
+The **quiet** option suppresses the progress output when set.
Note: `:` is a restricted character and cannot be part of the file name.
+
**podman [GLOBAL OPTIONS]**
**podman load [GLOBAL OPTIONS]**
-**podman load [OPTIONS] NAME[:TAG|@DIGEST]**
+**podman load [OPTIONS] NAME[:TAG]**
## OPTIONS
@@ -28,7 +30,7 @@ The remote client requires the use of this option.
**--quiet, -q**
-Suppress the output
+Suppress the progress output
**--signature-policy="PATHNAME"**
@@ -75,7 +77,7 @@ Loaded image: registry.fedoraproject.org/fedora:latest
```
## SEE ALSO
-podman(1), podman-save(1), crio(8)
+podman(1), podman-save(1), podman-tag(1), crio(8)
## HISTORY
July 2017, Originally compiled by Urvashi Mohnani <umohnani@redhat.com>
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-ps.1.md b/docs/podman-ps.1.md
index b8b1c3d62..811fbbc2f 100644
--- a/docs/podman-ps.1.md
+++ b/docs/podman-ps.1.md
@@ -148,6 +148,15 @@ CONTAINER ID IMAGE COMMAND CREATED STATUS
69ed779d8ef9f redis:alpine "redis-server" 25 hours ago Created 6379/tcp k8s_container1_podsandbox1_redhat.test.crio_redhat-test-crio_1
02f65160e14ca redis:alpine "redis-server" 19 hours ago Exited (-1) 19 hours ago 6379/tcp k8s_podsandbox1-redis_podsandbox1_redhat.test.crio_redhat-test-crio_0
```
+
+```
+$ podman ps
+CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
+4089df24d4f3 docker.io/library/centos:latest /bin/bash 2 minutes ago Up 2 minutes ago 0.0.0.0:80->8080/tcp, 0.0.0.0:2000-2006->2000-2006/tcp manyports
+92f58933c28c docker.io/library/centos:latest /bin/bash 3 minutes ago Up 3 minutes ago 192.168.99.100:1000-1006->1000-1006/tcp zen_sanderson
+
+```
+
## ps
Print a list of containers
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/docs/podman-save.1.md b/docs/podman-save.1.md
index a0b04d633..75aeda797 100644
--- a/docs/podman-save.1.md
+++ b/docs/podman-save.1.md
@@ -1,14 +1,13 @@
% podman-save(1)
## NAME
-podman\-save - Save an image to docker-archive or oci-archive
+podman\-save - Save an image to a container archive
## SYNOPSIS
**podman save** [*options*] *name*[:*tag*]
## DESCRIPTION
-**podman save** saves an image to either **docker-archive**, **oci-archive**, **oci-dir** (directory
-with oci manifest type), or **docker-dir** (directory with v2s2 manifest type) on the local machine,
+**podman save** saves an image to either **docker-archive**, **oci-archive**, **oci-dir** (directory with oci manifest type), or **docker-dir** (directory with v2s2 manifest type) on the local machine,
default is **docker-archive**. **podman save** writes to STDOUT by default and can be redirected to a
file using the **output** flag. The **quiet** flag suppresses the output when set.
Note: `:` is a restricted character and cannot be part of the file name.
diff --git a/install.md b/install.md
index 071eeff67..5fe150db2 100644
--- a/install.md
+++ b/install.md
@@ -268,7 +268,6 @@ registries = []
# If you need to block pull access from a registry, uncomment the section below
# and add the registries fully-qualified name.
#
-# Docker only
[registries.block]
registries = []
```
diff --git a/libpod/container_internal.go b/libpod/container_internal.go
index 872802016..ac2d65342 100644
--- a/libpod/container_internal.go
+++ b/libpod/container_internal.go
@@ -1429,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..e998ad316
--- /dev/null
+++ b/libpod/container_log.go
@@ -0,0 +1,213 @@
+package libpod
+
+import (
+ "fmt"
+ "io/ioutil"
+ "os"
+ "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 {
+ // If the log file does not exist, this is not fatal.
+ if os.IsNotExist(errors.Cause(err)) {
+ return 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{MustExist: true, 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 f09529a05..139600982 100644
--- a/libpod/events.go
+++ b/libpod/events.go
@@ -92,5 +92,5 @@ func (r *Runtime) getTail(fromStart, stream bool) (*tail.Tail, error) {
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 9836b7aab..b3b75d791 100644
--- a/libpod/runtime.go
+++ b/libpod/runtime.go
@@ -505,24 +505,33 @@ func newRuntimeFromConfig(userConfigPath string, options ...RuntimeOption) (runt
return nil, errors.Wrapf(err, "error configuring runtime")
}
}
- if err := makeRuntime(runtime); err != nil {
- return nil, err
- }
-
- if !foundConfig && rootlessConfigPath != "" {
- os.MkdirAll(filepath.Dir(rootlessConfigPath), 0755)
- file, err := os.OpenFile(rootlessConfigPath, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666)
- if err != nil && !os.IsExist(err) {
- return nil, errors.Wrapf(err, "cannot open file %s", rootlessConfigPath)
+ if rootlessConfigPath != "" {
+ // storage.conf
+ storageConfFile := util.StorageConfigFile()
+ if _, err := os.Stat(storageConfFile); os.IsNotExist(err) {
+ if err := util.WriteStorageConfigFile(&runtime.config.StorageConfig, storageConfFile); err != nil {
+ return nil, errors.Wrapf(err, "cannot write config file %s", storageConfFile)
+ }
}
- if err == nil {
- defer file.Close()
- enc := toml.NewEncoder(file)
- if err := enc.Encode(runtime.config); err != nil {
- os.Remove(rootlessConfigPath)
+
+ if !foundConfig {
+ os.MkdirAll(filepath.Dir(rootlessConfigPath), 0755)
+ file, err := os.OpenFile(rootlessConfigPath, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666)
+ if err != nil && !os.IsExist(err) {
+ return nil, errors.Wrapf(err, "cannot open file %s", rootlessConfigPath)
+ }
+ if err == nil {
+ defer file.Close()
+ enc := toml.NewEncoder(file)
+ if err := enc.Encode(runtime.config); err != nil {
+ os.Remove(rootlessConfigPath)
+ }
}
}
}
+ if err := makeRuntime(runtime); err != nil {
+ return nil, err
+ }
return runtime, nil
}
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/util/utils.go b/pkg/util/utils.go
index 73dddf2ac..a408ad34b 100644
--- a/pkg/util/utils.go
+++ b/pkg/util/utils.go
@@ -195,7 +195,7 @@ func GetRootlessRuntimeDir() (string, error) {
}
}
if runtimeDir == "" {
- tmpDir := filepath.Join(os.TempDir(), fmt.Sprintf("libpod-rundir-%s", uid))
+ tmpDir := filepath.Join(os.TempDir(), fmt.Sprintf("run-%s", uid))
os.MkdirAll(tmpDir, 0700)
st, err := os.Stat(tmpDir)
if err == nil && int(st.Sys().(*syscall.Stat_t).Uid) == os.Geteuid() && st.Mode().Perm() == 0700 {
@@ -311,36 +311,37 @@ func GetDefaultStoreOptions() (storage.StoreOptions, error) {
storageOpts = storage.StoreOptions{}
storage.ReloadConfigurationFile(storageConf, &storageOpts)
}
-
- if rootless.IsRootless() {
- if os.IsNotExist(err) {
- os.MkdirAll(filepath.Dir(storageConf), 0755)
- file, err := os.OpenFile(storageConf, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666)
- if err != nil {
- return storageOpts, errors.Wrapf(err, "cannot open %s", storageConf)
- }
-
- tomlConfiguration := getTomlStorage(&storageOpts)
- defer file.Close()
- enc := toml.NewEncoder(file)
- if err := enc.Encode(tomlConfiguration); err != nil {
- os.Remove(storageConf)
- }
- } else if err == nil {
- // If the file did not specify a graphroot or runroot,
- // set sane defaults so we don't try and use root-owned
- // directories
- if storageOpts.RunRoot == "" {
- storageOpts.RunRoot = defaultRootlessRunRoot
- }
- if storageOpts.GraphRoot == "" {
- storageOpts.GraphRoot = defaultRootlessGraphRoot
- }
+ if rootless.IsRootless() && err == nil {
+ // If the file did not specify a graphroot or runroot,
+ // set sane defaults so we don't try and use root-owned
+ // directories
+ if storageOpts.RunRoot == "" {
+ storageOpts.RunRoot = defaultRootlessRunRoot
+ }
+ if storageOpts.GraphRoot == "" {
+ storageOpts.GraphRoot = defaultRootlessGraphRoot
}
}
return storageOpts, nil
}
+// WriteStorageConfigFile writes the configuration to a file
+func WriteStorageConfigFile(storageOpts *storage.StoreOptions, storageConf string) error {
+ os.MkdirAll(filepath.Dir(storageConf), 0755)
+ file, err := os.OpenFile(storageConf, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666)
+ if err != nil {
+ return errors.Wrapf(err, "cannot open %s", storageConf)
+ }
+ tomlConfiguration := getTomlStorage(storageOpts)
+ defer file.Close()
+ enc := toml.NewEncoder(file)
+ if err := enc.Encode(tomlConfiguration); err != nil {
+ os.Remove(storageConf)
+ return err
+ }
+ return nil
+}
+
// StorageConfigFile returns the path to the storage config file used
func StorageConfigFile() string {
if rootless.IsRootless() {
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/logs_test.go b/test/e2e/logs_test.go
index b7d959de9..d051e3dba 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,40 @@ 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())
+ })
+
+ It("podman logs on a created container should result in 0 exit code", func() {
+ session := podmanTest.Podman([]string{"create", "-dt", "--name", "log", ALPINE})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(BeZero())
+
+ results := podmanTest.Podman([]string{"logs", "log"})
+ results.WaitWithDefaultTimeout()
+ Expect(results.ExitCode()).To(BeZero())
+ })
})
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/rm_test.go b/test/e2e/rm_test.go
index 1f67780da..9bf742a63 100644
--- a/test/e2e/rm_test.go
+++ b/test/e2e/rm_test.go
@@ -139,9 +139,23 @@ var _ = Describe("Podman rm", func() {
Expect(podmanTest.NumberOfContainers()).To(Equal(1))
})
+
It("podman rm bogus container", func() {
session := podmanTest.Podman([]string{"rm", "bogus"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(1))
})
+ It("podman rm bogus container and a running container", func() {
+ session := podmanTest.RunTopContainer("test1")
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ session = podmanTest.Podman([]string{"rm", "bogus", "test1"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(125))
+
+ session = podmanTest.Podman([]string{"rm", "test1", "bogus"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(125))
+ })
})
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)
})