summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmd/podman/containers/logs.go2
-rw-r--r--libpod/container_api.go2
-rw-r--r--libpod/container_log.go23
-rw-r--r--libpod/container_log_linux.go16
-rw-r--r--libpod/container_log_unsupported.go4
-rw-r--r--libpod/image/image.go6
-rw-r--r--libpod/runtime_img.go64
-rw-r--r--pkg/api/handlers/compat/containers_create.go33
-rw-r--r--pkg/api/handlers/compat/containers_logs.go76
-rw-r--r--pkg/domain/infra/abi/containers.go2
-rw-r--r--pkg/varlinkapi/containers.go2
-rw-r--r--test/apiv2/20-containers.at13
-rw-r--r--test/e2e/logs_test.go5
13 files changed, 167 insertions, 81 deletions
diff --git a/cmd/podman/containers/logs.go b/cmd/podman/containers/logs.go
index 850cb2e1f..966998877 100644
--- a/cmd/podman/containers/logs.go
+++ b/cmd/podman/containers/logs.go
@@ -32,6 +32,8 @@ var (
Long: logsDescription,
Args: func(cmd *cobra.Command, args []string) error {
switch {
+ case registry.IsRemote() && logsOptions.Latest:
+ return errors.New(cmd.Name() + " does not support 'latest' when run remotely")
case registry.IsRemote() && len(args) > 1:
return errors.New(cmd.Name() + " does not support multiple containers when run remotely")
case logsOptions.Latest && len(args) > 0:
diff --git a/libpod/container_api.go b/libpod/container_api.go
index b37b05ff2..c7df9d66c 100644
--- a/libpod/container_api.go
+++ b/libpod/container_api.go
@@ -353,7 +353,7 @@ func (c *Container) HTTPAttach(httpCon net.Conn, httpBuf *bufio.ReadWriter, stre
logOpts.WaitGroup.Wait()
close(logChan)
}()
- if err := c.ReadLog(logOpts, logChan); err != nil {
+ if err := c.ReadLog(context.Background(), logOpts, logChan); err != nil {
return err
}
logrus.Debugf("Done reading logs for container %s, %d bytes", c.ID(), logSize)
diff --git a/libpod/container_log.go b/libpod/container_log.go
index 97936c683..80f8e6e50 100644
--- a/libpod/container_log.go
+++ b/libpod/container_log.go
@@ -1,6 +1,7 @@
package libpod
import (
+ "context"
"fmt"
"os"
"time"
@@ -13,9 +14,9 @@ import (
)
// Log is a runtime function that can read one or more container logs.
-func (r *Runtime) Log(containers []*Container, options *logs.LogOptions, logChannel chan *logs.LogLine) error {
+func (r *Runtime) Log(ctx context.Context, containers []*Container, options *logs.LogOptions, logChannel chan *logs.LogLine) error {
for _, ctr := range containers {
- if err := ctr.ReadLog(options, logChannel); err != nil {
+ if err := ctr.ReadLog(ctx, options, logChannel); err != nil {
return err
}
}
@@ -23,25 +24,25 @@ func (r *Runtime) Log(containers []*Container, options *logs.LogOptions, logChan
}
// ReadLog reads a containers log based on the input options and returns loglines over a channel.
-func (c *Container) ReadLog(options *logs.LogOptions, logChannel chan *logs.LogLine) error {
+func (c *Container) ReadLog(ctx context.Context, options *logs.LogOptions, logChannel chan *logs.LogLine) error {
switch c.LogDriver() {
case define.NoLogging:
return errors.Wrapf(define.ErrNoLogs, "this container is using the 'none' log driver, cannot read logs")
case define.JournaldLogging:
// TODO Skip sending logs until journald logs can be read
- return c.readFromJournal(options, logChannel)
+ return c.readFromJournal(ctx, options, logChannel)
case define.JSONLogging:
// TODO provide a separate implementation of this when Conmon
// has support.
fallthrough
case define.KubernetesLogging, "":
- return c.readFromLogFile(options, logChannel)
+ return c.readFromLogFile(ctx, options, logChannel)
default:
return errors.Wrapf(define.ErrInternal, "unrecognized log driver %q, cannot read logs", c.LogDriver())
}
}
-func (c *Container) readFromLogFile(options *logs.LogOptions, logChannel chan *logs.LogLine) error {
+func (c *Container) readFromLogFile(ctx context.Context, options *logs.LogOptions, logChannel chan *logs.LogLine) error {
t, tailLog, err := logs.GetLogFile(c.LogPath(), options)
if err != nil {
// If the log file does not exist, this is not fatal.
@@ -62,8 +63,17 @@ func (c *Container) readFromLogFile(options *logs.LogOptions, logChannel chan *l
}
go func() {
+ defer options.WaitGroup.Done()
+
var partial string
for line := range t.Lines {
+ select {
+ case <-ctx.Done():
+ // the consumer has cancelled
+ return
+ default:
+ // fallthrough
+ }
nll, err := logs.NewLogLine(line.Text)
if err != nil {
logrus.Error(err)
@@ -82,7 +92,6 @@ func (c *Container) readFromLogFile(options *logs.LogOptions, logChannel chan *l
logChannel <- nll
}
}
- options.WaitGroup.Done()
}()
// Check if container is still running or paused
if options.Follow {
diff --git a/libpod/container_log_linux.go b/libpod/container_log_linux.go
index fad3bf87c..00b2039a9 100644
--- a/libpod/container_log_linux.go
+++ b/libpod/container_log_linux.go
@@ -4,6 +4,7 @@
package libpod
import (
+ "context"
"fmt"
"io"
"math"
@@ -29,7 +30,7 @@ const (
bufLen = 16384
)
-func (c *Container) readFromJournal(options *logs.LogOptions, logChannel chan *logs.LogLine) error {
+func (c *Container) readFromJournal(ctx context.Context, options *logs.LogOptions, logChannel chan *logs.LogLine) error {
var config journal.JournalReaderConfig
if options.Tail < 0 {
config.NumFromTail = math.MaxUint64
@@ -65,13 +66,24 @@ func (c *Container) readFromJournal(options *logs.LogOptions, logChannel chan *l
if options.Follow {
go func() {
+ done := make(chan bool)
+ until := make(chan time.Time)
+ go func() {
+ select {
+ case <-ctx.Done():
+ until <- time.Time{}
+ case <-done:
+ // nothing to do anymore
+ }
+ }()
follower := FollowBuffer{logChannel}
- err := r.Follow(nil, follower)
+ err := r.Follow(until, follower)
if err != nil {
logrus.Debugf(err.Error())
}
r.Close()
options.WaitGroup.Done()
+ done <- true
return
}()
return nil
diff --git a/libpod/container_log_unsupported.go b/libpod/container_log_unsupported.go
index 18882720a..f3b36619e 100644
--- a/libpod/container_log_unsupported.go
+++ b/libpod/container_log_unsupported.go
@@ -3,11 +3,13 @@
package libpod
import (
+ "context"
+
"github.com/containers/libpod/v2/libpod/define"
"github.com/containers/libpod/v2/libpod/logs"
"github.com/pkg/errors"
)
-func (c *Container) readFromJournal(options *logs.LogOptions, logChannel chan *logs.LogLine) error {
+func (c *Container) readFromJournal(_ context.Context, _ *logs.LogOptions, _ chan *logs.LogLine) error {
return errors.Wrapf(define.ErrOSNotSupported, "Journald logging only enabled with systemd on linux")
}
diff --git a/libpod/image/image.go b/libpod/image/image.go
index 048ec825d..e2bd1ad5d 100644
--- a/libpod/image/image.go
+++ b/libpod/image/image.go
@@ -21,6 +21,7 @@ import (
"github.com/containers/image/v5/image"
"github.com/containers/image/v5/manifest"
ociarchive "github.com/containers/image/v5/oci/archive"
+ "github.com/containers/image/v5/oci/layout"
is "github.com/containers/image/v5/storage"
"github.com/containers/image/v5/tarball"
"github.com/containers/image/v5/transports"
@@ -1483,9 +1484,10 @@ func (i *Image) Save(ctx context.Context, source, format, output string, moreTag
return errors.Wrapf(err, "error getting OCI archive ImageReference for (%q, %q)", output, destImageName)
}
case "oci-dir":
- destRef, err = directory.NewReference(output)
+ destImageName := imageNameForSaveDestination(i, source)
+ destRef, err = layout.NewReference(output, destImageName) // destImageName may be ""
if err != nil {
- return errors.Wrapf(err, "error getting directory ImageReference for %q", output)
+ return errors.Wrapf(err, "error getting the OCI directory ImageReference for (%q, %q)", output, destImageName)
}
manifestType = imgspecv1.MediaTypeImageManifest
case "docker-dir":
diff --git a/libpod/runtime_img.go b/libpod/runtime_img.go
index eab05f34d..370c9d610 100644
--- a/libpod/runtime_img.go
+++ b/libpod/runtime_img.go
@@ -8,9 +8,15 @@ import (
"net/http"
"net/url"
"os"
+ "strings"
"github.com/containers/buildah/imagebuildah"
+ "github.com/containers/image/v5/directory"
"github.com/containers/image/v5/docker/reference"
+ ociarchive "github.com/containers/image/v5/oci/archive"
+ "github.com/containers/image/v5/oci/layout"
+ "github.com/containers/image/v5/types"
+
"github.com/containers/libpod/v2/libpod/define"
"github.com/containers/libpod/v2/libpod/image"
"github.com/containers/libpod/v2/pkg/util"
@@ -18,9 +24,7 @@ import (
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
- "github.com/containers/image/v5/directory"
dockerarchive "github.com/containers/image/v5/docker/archive"
- ociarchive "github.com/containers/image/v5/oci/archive"
v1 "github.com/opencontainers/image-spec/specs-go/v1"
)
@@ -256,28 +260,48 @@ func DownloadFromFile(reader *os.File) (string, error) {
// LoadImage loads a container image into local storage
func (r *Runtime) LoadImage(ctx context.Context, name, inputFile string, writer io.Writer, signaturePolicy string) (string, error) {
- var newImages []*image.Image
- src, err := dockerarchive.ParseReference(inputFile) // FIXME? We should add dockerarchive.NewReference()
- if err == nil {
- newImages, err = r.ImageRuntime().LoadFromArchiveReference(ctx, src, signaturePolicy, writer)
- }
- if err != nil {
- // generate full src name with specified image:tag
- src, err := ociarchive.NewReference(inputFile, name) // imageName may be ""
- if err == nil {
- newImages, err = r.ImageRuntime().LoadFromArchiveReference(ctx, src, signaturePolicy, writer)
- }
- if err != nil {
- src, err := directory.NewReference(inputFile)
- if err == nil {
- newImages, err = r.ImageRuntime().LoadFromArchiveReference(ctx, src, signaturePolicy, writer)
+ var (
+ newImages []*image.Image
+ err error
+ src types.ImageReference
+ )
+
+ for _, referenceFn := range []func() (types.ImageReference, error){
+ func() (types.ImageReference, error) {
+ return dockerarchive.ParseReference(inputFile) // FIXME? We should add dockerarchive.NewReference()
+ },
+ func() (types.ImageReference, error) {
+ return ociarchive.NewReference(inputFile, name) // name may be ""
+ },
+ func() (types.ImageReference, error) {
+ // prepend "localhost/" to support local image saved with this semantics
+ if !strings.Contains(name, "/") {
+ return ociarchive.NewReference(inputFile, fmt.Sprintf("%s/%s", image.DefaultLocalRegistry, name))
+ }
+ return nil, nil
+ },
+ func() (types.ImageReference, error) {
+ return directory.NewReference(inputFile)
+ },
+ func() (types.ImageReference, error) {
+ return layout.NewReference(inputFile, name)
+ },
+ func() (types.ImageReference, error) {
+ // prepend "localhost/" to support local image saved with this semantics
+ if !strings.Contains(name, "/") {
+ return layout.NewReference(inputFile, fmt.Sprintf("%s/%s", image.DefaultLocalRegistry, name))
}
- if err != nil {
- return "", errors.Wrapf(err, "error pulling %q", name)
+ return nil, nil
+ },
+ } {
+ src, err = referenceFn()
+ if err == nil && src != nil {
+ if newImages, err = r.ImageRuntime().LoadFromArchiveReference(ctx, src, signaturePolicy, writer); err == nil {
+ return getImageNames(newImages), nil
}
}
}
- return getImageNames(newImages), nil
+ return "", errors.Wrapf(err, "error pulling %q", name)
}
func getImageNames(images []*image.Image) string {
diff --git a/pkg/api/handlers/compat/containers_create.go b/pkg/api/handlers/compat/containers_create.go
index 8034a529c..031e30b7e 100644
--- a/pkg/api/handlers/compat/containers_create.go
+++ b/pkg/api/handlers/compat/containers_create.go
@@ -1,6 +1,7 @@
package compat
import (
+ "context"
"encoding/json"
"fmt"
"net/http"
@@ -40,6 +41,7 @@ func CreateContainer(w http.ResponseWriter, r *http.Request) {
}
if len(input.HostConfig.Links) > 0 {
utils.Error(w, utils.ErrLinkNotSupport.Error(), http.StatusBadRequest, errors.Wrapf(utils.ErrLinkNotSupport, "bad parameter"))
+ return
}
newImage, err := runtime.ImageRuntime().NewFromLocal(input.Image)
if err != nil {
@@ -51,7 +53,7 @@ func CreateContainer(w http.ResponseWriter, r *http.Request) {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "GetConfig()"))
return
}
- cc, err := makeCreateConfig(containerConfig, input, newImage)
+ cc, err := makeCreateConfig(r.Context(), containerConfig, input, newImage)
if err != nil {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "makeCreatConfig()"))
return
@@ -60,7 +62,7 @@ func CreateContainer(w http.ResponseWriter, r *http.Request) {
utils.CreateContainer(r.Context(), w, runtime, &cc)
}
-func makeCreateConfig(containerConfig *config.Config, input handlers.CreateContainerConfig, newImage *image2.Image) (createconfig.CreateConfig, error) {
+func makeCreateConfig(ctx context.Context, containerConfig *config.Config, input handlers.CreateContainerConfig, newImage *image2.Image) (createconfig.CreateConfig, error) {
var (
err error
init bool
@@ -79,6 +81,22 @@ func makeCreateConfig(containerConfig *config.Config, input handlers.CreateConta
workDir = input.WorkingDir
}
+ if len(input.Entrypoint) == 0 {
+ entrypointSlice, err := newImage.Entrypoint(ctx)
+ if err != nil {
+ return createconfig.CreateConfig{}, err
+ }
+ input.Entrypoint = entrypointSlice
+ }
+
+ if len(input.Cmd) == 0 {
+ cmdSlice, err := newImage.Cmd(ctx)
+ if err != nil {
+ return createconfig.CreateConfig{}, err
+ }
+ input.Cmd = cmdSlice
+ }
+
stopTimeout := containerConfig.Engine.StopTimeout
if input.StopTimeout != nil {
stopTimeout = uint(*input.StopTimeout)
@@ -217,5 +235,16 @@ func makeCreateConfig(containerConfig *config.Config, input handlers.CreateConta
Pid: pidConfig,
}
+
+ fullCmd := append(input.Entrypoint, input.Cmd...)
+ if len(fullCmd) > 0 {
+ m.PodmanPath = fullCmd[0]
+ if len(fullCmd) == 1 {
+ m.Args = fullCmd
+ } else {
+ m.Args = fullCmd[1:]
+ }
+ }
+
return m, nil
}
diff --git a/pkg/api/handlers/compat/containers_logs.go b/pkg/api/handlers/compat/containers_logs.go
index 8147f4d38..30ee030e8 100644
--- a/pkg/api/handlers/compat/containers_logs.go
+++ b/pkg/api/handlers/compat/containers_logs.go
@@ -92,7 +92,7 @@ func LogsFromContainer(w http.ResponseWriter, r *http.Request) {
options.WaitGroup = &wg
logChannel := make(chan *logs.LogLine, tail+1)
- if err := runtime.Log([]*libpod.Container{ctnr}, options, logChannel); err != nil {
+ if err := runtime.Log(r.Context(), []*libpod.Container{ctnr}, options, logChannel); err != nil {
utils.InternalServerError(w, errors.Wrapf(err, "Failed to obtain logs for Container '%s'", name))
return
}
@@ -105,50 +105,48 @@ func LogsFromContainer(w http.ResponseWriter, r *http.Request) {
var frame strings.Builder
header := make([]byte, 8)
- for ok := true; ok; ok = query.Follow {
- for line := range logChannel {
- if _, found := r.URL.Query()["until"]; found {
- if line.Time.After(until) {
- break
- }
+ for line := range logChannel {
+ if _, found := r.URL.Query()["until"]; found {
+ if line.Time.After(until) {
+ break
}
+ }
- // Reset buffer we're ready to loop again
- frame.Reset()
- switch line.Device {
- case "stdout":
- if !query.Stdout {
- continue
- }
- header[0] = 1
- case "stderr":
- if !query.Stderr {
- continue
- }
- header[0] = 2
- default:
- // Logging and moving on is the best we can do here. We may have already sent
- // a Status and Content-Type to client therefore we can no longer report an error.
- log.Infof("unknown Device type '%s' in log file from Container %s", line.Device, ctnr.ID())
+ // Reset buffer we're ready to loop again
+ frame.Reset()
+ switch line.Device {
+ case "stdout":
+ if !query.Stdout {
continue
}
-
- if query.Timestamps {
- frame.WriteString(line.Time.Format(time.RFC3339))
- frame.WriteString(" ")
+ header[0] = 1
+ case "stderr":
+ if !query.Stderr {
+ continue
}
- frame.WriteString(line.Msg)
+ header[0] = 2
+ default:
+ // Logging and moving on is the best we can do here. We may have already sent
+ // a Status and Content-Type to client therefore we can no longer report an error.
+ log.Infof("unknown Device type '%s' in log file from Container %s", line.Device, ctnr.ID())
+ continue
+ }
- binary.BigEndian.PutUint32(header[4:], uint32(frame.Len()))
- if _, err := w.Write(header[0:8]); err != nil {
- log.Errorf("unable to write log output header: %q", err)
- }
- if _, err := io.WriteString(w, frame.String()); err != nil {
- log.Errorf("unable to write frame string: %q", err)
- }
- if flusher, ok := w.(http.Flusher); ok {
- flusher.Flush()
- }
+ if query.Timestamps {
+ frame.WriteString(line.Time.Format(time.RFC3339))
+ frame.WriteString(" ")
+ }
+ frame.WriteString(line.Msg)
+
+ binary.BigEndian.PutUint32(header[4:], uint32(frame.Len()))
+ if _, err := w.Write(header[0:8]); err != nil {
+ log.Errorf("unable to write log output header: %q", err)
+ }
+ if _, err := io.WriteString(w, frame.String()); err != nil {
+ log.Errorf("unable to write frame string: %q", err)
+ }
+ if flusher, ok := w.(http.Flusher); ok {
+ flusher.Flush()
}
}
}
diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go
index 596fc2cc1..8909f831d 100644
--- a/pkg/domain/infra/abi/containers.go
+++ b/pkg/domain/infra/abi/containers.go
@@ -924,7 +924,7 @@ func (ic *ContainerEngine) ContainerLogs(ctx context.Context, containers []strin
}
logChannel := make(chan *logs.LogLine, chSize)
- if err := ic.Libpod.Log(ctrs, logOpts, logChannel); err != nil {
+ if err := ic.Libpod.Log(ctx, ctrs, logOpts, logChannel); err != nil {
return err
}
diff --git a/pkg/varlinkapi/containers.go b/pkg/varlinkapi/containers.go
index 8650ba000..07b492331 100644
--- a/pkg/varlinkapi/containers.go
+++ b/pkg/varlinkapi/containers.go
@@ -754,7 +754,7 @@ func (i *VarlinkAPI) GetContainersLogs(call iopodman.VarlinkCall, names []string
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
- if err := i.Runtime.Log(containers, &options, logChannel); err != nil {
+ if err := i.Runtime.Log(getContext(), containers, &options, logChannel); err != nil {
return err
}
go func() {
diff --git a/test/apiv2/20-containers.at b/test/apiv2/20-containers.at
index 25843e61c..9a1db5154 100644
--- a/test/apiv2/20-containers.at
+++ b/test/apiv2/20-containers.at
@@ -51,6 +51,19 @@ cid=$(jq -r '.[0].Id' <<<"$output")
t DELETE libpod/containers/$cid 204
+# Ensure that API does not occur: Create Container creates an invalid and the container fails to start
+# https://github.com/containers/libpod/issues/6799
+CNAME=testArgs
+t POST libpod/containers/create?name=${CNAME} Image=${IMAGE} 201 \
+ .Id~[0-9a-f]\\{64\\}
+t GET libpod/containers/json?limit=1 200 \
+ length=1 \
+ .[0].Id~[0-9a-f]\\{64\\}
+cid=$(jq -r '.[0].Id' <<<"$output")
+# This step should start the container properly
+t POST libpod/containers/${cid}/start '' 204
+t DELETE libpod/containers/$cid 204
+
CNAME=myfoo
podman run --name $CNAME $IMAGE -td top
t GET libpod/containers/json?all=true 200 \
diff --git a/test/e2e/logs_test.go b/test/e2e/logs_test.go
index 1ceab9161..dd69e4ce8 100644
--- a/test/e2e/logs_test.go
+++ b/test/e2e/logs_test.go
@@ -121,7 +121,6 @@ var _ = Describe("Podman logs", func() {
})
It("latest and container name should fail", func() {
- SkipIfRemote() // -l not supported
results := podmanTest.Podman([]string{"logs", "-l", "foobar"})
results.WaitWithDefaultTimeout()
Expect(results).To(ExitWithError())
@@ -159,7 +158,6 @@ var _ = Describe("Podman logs", func() {
})
It("using journald for container with container tag", func() {
- SkipIfRemote()
Skip("need to verify images have correct packages for journald")
logc := podmanTest.Podman([]string{"run", "--log-driver", "journald", "--log-opt=tag={{.ImageName}}", "-d", ALPINE, "sh", "-c", "echo podman; sleep 0.1; echo podman; sleep 0.1; echo podman"})
logc.WaitWithDefaultTimeout()
@@ -178,7 +176,6 @@ var _ = Describe("Podman logs", func() {
It("using journald for container name", func() {
Skip("need to verify images have correct packages for journald")
- SkipIfRemote()
containerName := "inside-journal"
logc := podmanTest.Podman([]string{"run", "--log-driver", "journald", "-d", "--name", containerName, ALPINE, "sh", "-c", "echo podman; sleep 0.1; echo podman; sleep 0.1; echo podman"})
logc.WaitWithDefaultTimeout()
@@ -273,7 +270,6 @@ var _ = Describe("Podman logs", func() {
})
It("streaming output", func() {
- Skip(v2remotefail)
containerName := "logs-f-rm"
logc := podmanTest.Podman([]string{"run", "--rm", "--name", containerName, "-dt", ALPINE, "sh", "-c", "echo podman; sleep 1; echo podman"})
@@ -314,7 +310,6 @@ var _ = Describe("Podman logs", func() {
})
It("follow output stopped container", func() {
- Skip(v2remotefail)
containerName := "logs-f"
logc := podmanTest.Podman([]string{"run", "--name", containerName, "-d", ALPINE, "true"})