summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmd/podman/containers/logs.go3
-rw-r--r--docs/source/markdown/podman-create.1.md4
-rw-r--r--docs/source/markdown/podman-run.1.md4
-rw-r--r--libpod/logs/log.go16
-rw-r--r--pkg/api/handlers/compat/containers_create.go15
-rw-r--r--pkg/api/handlers/compat/networks.go6
-rw-r--r--pkg/bindings/generator/generator.go234
-rw-r--r--pkg/bindings/images/removeoptions_types.go93
-rw-r--r--pkg/bindings/images/rm.go10
-rw-r--r--pkg/bindings/images/types.go8
-rw-r--r--pkg/bindings/test/images_test.go17
-rw-r--r--pkg/bindings/test/manifests_test.go2
-rw-r--r--pkg/domain/entities/container_ps.go2
-rw-r--r--pkg/domain/entities/containers.go6
-rw-r--r--pkg/domain/infra/abi/containers.go4
-rw-r--r--pkg/domain/infra/tunnel/containers.go53
-rw-r--r--pkg/ps/ps.go37
-rw-r--r--test/apiv2/20-containers.at11
-rw-r--r--test/apiv2/35-networks.at8
-rw-r--r--test/e2e/logs_test.go17
-rw-r--r--test/e2e/play_kube_test.go4
-rw-r--r--test/e2e/start_test.go23
-rw-r--r--test/e2e/toolbox_test.go2
23 files changed, 521 insertions, 58 deletions
diff --git a/cmd/podman/containers/logs.go b/cmd/podman/containers/logs.go
index d4ede370a..9b562afd8 100644
--- a/cmd/podman/containers/logs.go
+++ b/cmd/podman/containers/logs.go
@@ -122,6 +122,7 @@ func logs(_ *cobra.Command, args []string) error {
}
logsOptions.Since = since
}
- logsOptions.Writer = os.Stdout
+ logsOptions.StdoutWriter = os.Stdout
+ logsOptions.StderrWriter = os.Stderr
return registry.ContainerEngine().ContainerLogs(registry.GetContext(), args, logsOptions.ContainerLogsOptions)
}
diff --git a/docs/source/markdown/podman-create.1.md b/docs/source/markdown/podman-create.1.md
index b5f5591a9..8deaa8540 100644
--- a/docs/source/markdown/podman-create.1.md
+++ b/docs/source/markdown/podman-create.1.md
@@ -803,10 +803,6 @@ To generate systemd unit files, please see *podman generate systemd*
Automatically remove the container when it exits. The default is *false*.
-Note that the container will not be removed when it could not be created or
-started successfully. This allows the user to inspect the container after
-failure.
-
#### **--rootfs**
If specified, the first argument refers to an exploded container on the file system.
diff --git a/docs/source/markdown/podman-run.1.md b/docs/source/markdown/podman-run.1.md
index 3241cf9f7..cd45e53ef 100644
--- a/docs/source/markdown/podman-run.1.md
+++ b/docs/source/markdown/podman-run.1.md
@@ -840,10 +840,6 @@ To generate systemd unit files, please see **podman generate systemd**.
Automatically remove the container when it exits. The default is **false**.
-Note that the container will not be removed when it could not be created or
-started successfully. This allows the user to inspect the container after
-failure.
-
#### **--rmi**=*true|false*
After exit of the container, remove the image unless another
diff --git a/libpod/logs/log.go b/libpod/logs/log.go
index a9554088b..d3d83747f 100644
--- a/libpod/logs/log.go
+++ b/libpod/logs/log.go
@@ -210,3 +210,19 @@ func NewLogLine(line string) (*LogLine, error) {
func (l *LogLine) Partial() bool {
return l.ParseLogType == PartialLogType
}
+
+func (l *LogLine) Write(stdout io.Writer, stderr io.Writer, logOpts *LogOptions) {
+ switch l.Device {
+ case "stdout":
+ if stdout != nil {
+ fmt.Fprintln(stdout, l.String(logOpts))
+ }
+ case "stderr":
+ if stderr != nil {
+ fmt.Fprintln(stderr, l.String(logOpts))
+ }
+ default:
+ // Warn the user if the device type does not match. Most likely the file is corrupted.
+ logrus.Warnf("unknown Device type '%s' in log file from Container %s", l.Device, l.CID)
+ }
+}
diff --git a/pkg/api/handlers/compat/containers_create.go b/pkg/api/handlers/compat/containers_create.go
index 409a74de2..6e85872b2 100644
--- a/pkg/api/handlers/compat/containers_create.go
+++ b/pkg/api/handlers/compat/containers_create.go
@@ -66,7 +66,20 @@ func CreateContainer(w http.ResponseWriter, r *http.Request) {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "make cli opts()"))
return
}
- sg := specgen.NewSpecGenerator(newImage.ID(), cliOpts.RootFS)
+
+ imgNameOrID := newImage.ID()
+ // if the img had multi names with the same sha256 ID, should use the InputName, not the ID
+ if len(newImage.Names()) > 1 {
+ imageRef, err := utils.ParseDockerReference(newImage.InputName)
+ if err != nil {
+ utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, err)
+ return
+ }
+ // maybe the InputName has no tag, so use full name to display
+ imgNameOrID = imageRef.DockerReference().String()
+ }
+
+ sg := specgen.NewSpecGenerator(imgNameOrID, cliOpts.RootFS)
if err := common.FillOutSpecGen(sg, cliOpts, args); err != nil {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "fill out specgen"))
return
diff --git a/pkg/api/handlers/compat/networks.go b/pkg/api/handlers/compat/networks.go
index fe13971b0..f0b922885 100644
--- a/pkg/api/handlers/compat/networks.go
+++ b/pkg/api/handlers/compat/networks.go
@@ -131,7 +131,7 @@ func getNetworkResourceByNameOrID(nameOrID string, runtime *libpod.Runtime, filt
Name: conf.Name,
ID: network.GetNetworkID(conf.Name),
Created: time.Unix(int64(stat.Ctim.Sec), int64(stat.Ctim.Nsec)), // nolint: unconvert
- Scope: "",
+ Scope: "local",
Driver: network.DefaultNetworkDriver,
EnableIPv6: false,
IPAM: dockerNetwork.IPAM{
@@ -197,7 +197,7 @@ func ListNetworks(w http.ResponseWriter, r *http.Request) {
}
var reports []*types.NetworkResource
- logrus.Errorf("netNames: %q", strings.Join(netNames, ", "))
+ logrus.Debugf("netNames: %q", strings.Join(netNames, ", "))
for _, name := range netNames {
report, err := getNetworkResourceByNameOrID(name, runtime, query.Filters)
if err != nil {
@@ -239,7 +239,7 @@ func CreateNetwork(w http.ResponseWriter, r *http.Request) {
Internal: networkCreate.Internal,
Labels: networkCreate.Labels,
}
- if networkCreate.IPAM != nil && networkCreate.IPAM.Config != nil {
+ if networkCreate.IPAM != nil && len(networkCreate.IPAM.Config) > 0 {
if len(networkCreate.IPAM.Config) > 1 {
utils.InternalServerError(w, errors.New("compat network create can only support one IPAM config"))
return
diff --git a/pkg/bindings/generator/generator.go b/pkg/bindings/generator/generator.go
new file mode 100644
index 000000000..24c2310ff
--- /dev/null
+++ b/pkg/bindings/generator/generator.go
@@ -0,0 +1,234 @@
+package main
+
+import (
+ "errors"
+ "fmt"
+ "go/ast"
+ "go/parser"
+ "go/token"
+ "io/ioutil"
+ "os"
+ "os/exec"
+ "strings"
+ "text/template"
+ "time"
+)
+
+var bodyTmpl = `package {{.PackageName}}
+
+import (
+{{range $import := .Imports}} {{$import}}
+{{end}}
+
+)
+
+/*
+This file is generated automatically by go generate. Do not edit.
+
+Created {{.Date}}
+*/
+
+// Changed
+func (o *{{.StructName}}) Changed(fieldName string) bool {
+ r := reflect.ValueOf(o)
+ value := reflect.Indirect(r).FieldByName(fieldName)
+ return !value.IsNil()
+}
+
+// ToParams
+func (o *{{.StructName}}) ToParams() (url.Values, error) {
+ params := url.Values{}
+ if o == nil {
+ return params, nil
+ }
+ json := jsoniter.ConfigCompatibleWithStandardLibrary
+ s := reflect.ValueOf(o)
+ if reflect.Ptr == s.Kind() {
+ s = s.Elem()
+ }
+ sType := s.Type()
+ for i := 0; i < s.NumField(); i++ {
+ fieldName := sType.Field(i).Name
+ if !o.Changed(fieldName) {
+ continue
+ }
+ f := s.Field(i)
+ if reflect.Ptr == f.Kind() {
+ f = f.Elem()
+ }
+ switch f.Kind() {
+ case reflect.Bool:
+ params.Set(fieldName, strconv.FormatBool(f.Bool()))
+ case reflect.String:
+ params.Set(fieldName, f.String())
+ case reflect.Int, reflect.Int64:
+ // f.Int() is always an int64
+ params.Set(fieldName, strconv.FormatInt(f.Int(), 10))
+ case reflect.Slice:
+ typ := reflect.TypeOf(f.Interface()).Elem()
+ slice := reflect.MakeSlice(reflect.SliceOf(typ), f.Len(), f.Cap())
+ switch typ.Kind() {
+ case reflect.String:
+ s, ok := slice.Interface().([]string)
+ if !ok {
+ return nil, errors.New("failed to convert to string slice")
+ }
+ for _, val := range s {
+ params.Add(fieldName, val)
+ }
+ default:
+ return nil, errors.Errorf("unknown slice type %s", f.Kind().String())
+ }
+ case reflect.Map:
+ lowerCaseKeys := make(map[string][]string)
+ // I dont know if this code is needed anymore, TBD
+ // for k, v := range filters {
+ // lowerCaseKeys[strings.ToLower(k)] = v
+ // }
+ s, err := json.MarshalToString(lowerCaseKeys)
+ if err != nil {
+ return nil, err
+ }
+
+ params.Set(fieldName, s)
+ default:
+ return nil, errors.Errorf("unknown type %s", f.Kind().String())
+ }
+ }
+ return params, nil
+}
+`
+
+var fieldTmpl = `
+// With{{.Name}}
+func(o *{{.StructName}}) With{{.Name}}(value {{.Type}}) *{{.StructName}} {
+ v := &value
+ o.{{.Name}} = v
+ return o
+}
+`
+
+type fieldStruct struct {
+ Name string
+ StructName string
+ Type string
+}
+
+func main() {
+ var (
+ closed bool
+ fieldStructs []fieldStruct
+ structNode ast.Node
+ )
+ srcFile := os.Getenv("GOFILE")
+ pkg := os.Getenv("GOPACKAGE")
+ inputStructName := os.Args[1]
+ b, err := ioutil.ReadFile(srcFile)
+ if err != nil {
+ panic(err)
+ }
+ fset := token.NewFileSet() // positions are relative to fset
+ f, err := parser.ParseFile(fset, "", b, parser.ParseComments)
+ if err != nil {
+ panic(err)
+ }
+
+ // always add reflect
+ imports := []string{"\"reflect\""}
+ for _, imp := range f.Imports {
+ imports = append(imports, imp.Path.Value)
+ }
+
+ out, err := os.Create(strings.ToLower(inputStructName) + "_" + srcFile)
+ if err != nil {
+ panic(err)
+ }
+ defer func() {
+ if !closed {
+ out.Close()
+ }
+ }()
+ bodyStruct := struct {
+ PackageName string
+ Imports []string
+ Date string
+ StructName string
+ }{
+ PackageName: pkg,
+ Imports: imports,
+ Date: time.Now().String(),
+ StructName: inputStructName,
+ }
+
+ body := template.Must(template.New("body").Parse(bodyTmpl))
+ fields := template.Must(template.New("fields").Parse(fieldTmpl))
+ ast.Inspect(f, func(n ast.Node) bool {
+ ref, refOK := n.(*ast.TypeSpec)
+ if refOK {
+ if ref.Name.Name == inputStructName {
+ structNode = n
+ x := ref.Type.(*ast.StructType)
+ for _, field := range x.Fields.List {
+ var (
+ name string
+ )
+ typeExpr := field.Type
+ start := typeExpr.Pos() - 1
+ end := typeExpr.End() - 1
+ fieldType := strings.Replace(string(b[start:end]), "*", "", 1)
+ if len(field.Names) > 0 {
+ name = field.Names[0].Name
+ if len(name) < 1 {
+ panic(errors.New("bad name"))
+ }
+ }
+ fStruct := fieldStruct{
+ Name: name,
+ StructName: inputStructName,
+ Type: fieldType,
+ }
+ fieldStructs = append(fieldStructs, fStruct)
+ } // for
+
+ // create the body
+ if err := body.Execute(out, bodyStruct); err != nil {
+ fmt.Println(err)
+ os.Exit(1)
+ }
+
+ // create with func from the struct fields
+ for _, fs := range fieldStructs {
+ if err := fields.Execute(out, fs); err != nil {
+ fmt.Println(err)
+ os.Exit(1)
+ }
+ }
+
+ // close out file
+ if err := out.Close(); err != nil {
+ fmt.Println(err)
+ os.Exit(1)
+ }
+ closed = true
+
+ // go fmt file
+ gofmt := exec.Command("gofmt", "-w", "-s", out.Name())
+ gofmt.Stderr = os.Stdout
+ if err := gofmt.Run(); err != nil {
+ fmt.Println(err)
+ os.Exit(1)
+ }
+
+ // go import file
+ goimport := exec.Command("goimports", "-w", out.Name())
+ goimport.Stderr = os.Stdout
+ if err := goimport.Run(); err != nil {
+ fmt.Println(err)
+ os.Exit(1)
+ }
+ }
+
+ }
+ return true
+ })
+}
diff --git a/pkg/bindings/images/removeoptions_types.go b/pkg/bindings/images/removeoptions_types.go
new file mode 100644
index 000000000..5902bf908
--- /dev/null
+++ b/pkg/bindings/images/removeoptions_types.go
@@ -0,0 +1,93 @@
+package images
+
+import (
+ "net/url"
+ "reflect"
+ "strconv"
+
+ jsoniter "github.com/json-iterator/go"
+ "github.com/pkg/errors"
+)
+
+/*
+This file is generated automatically by go generate. Do not edit.
+
+Created 2020-12-10 12:51:06.090426622 -0600 CST m=+0.000133169
+*/
+
+// Changed
+func (o *RemoveOptions) Changed(fieldName string) bool {
+ r := reflect.ValueOf(o)
+ value := reflect.Indirect(r).FieldByName(fieldName)
+ return !value.IsNil()
+}
+
+// ToParams
+func (o *RemoveOptions) ToParams() (url.Values, error) {
+ params := url.Values{}
+ if o == nil {
+ return params, nil
+ }
+ json := jsoniter.ConfigCompatibleWithStandardLibrary
+ s := reflect.ValueOf(o)
+ if reflect.Ptr == s.Kind() {
+ s = s.Elem()
+ }
+ sType := s.Type()
+ for i := 0; i < s.NumField(); i++ {
+ fieldName := sType.Field(i).Name
+ if !o.Changed(fieldName) {
+ continue
+ }
+ f := s.Field(i)
+ if reflect.Ptr == f.Kind() {
+ f = f.Elem()
+ }
+ switch f.Kind() {
+ case reflect.Bool:
+ params.Set(fieldName, strconv.FormatBool(f.Bool()))
+ case reflect.String:
+ params.Set(fieldName, f.String())
+ case reflect.Int, reflect.Int64:
+ // f.Int() is always an int64
+ params.Set(fieldName, strconv.FormatInt(f.Int(), 10))
+ case reflect.Slice:
+ typ := reflect.TypeOf(f.Interface()).Elem()
+ slice := reflect.MakeSlice(reflect.SliceOf(typ), f.Len(), f.Cap())
+ switch typ.Kind() {
+ case reflect.String:
+ s, ok := slice.Interface().([]string)
+ if !ok {
+ return nil, errors.New("failed to convert to string slice")
+ }
+ for _, val := range s {
+ params.Add(fieldName, val)
+ }
+ default:
+ return nil, errors.Errorf("unknown slice type %s", f.Kind().String())
+ }
+ case reflect.Map:
+ lowerCaseKeys := make(map[string][]string)
+ // I dont know if this code is needed anymore, TBD
+ // for k, v := range filters {
+ // lowerCaseKeys[strings.ToLower(k)] = v
+ // }
+ s, err := json.MarshalToString(lowerCaseKeys)
+ if err != nil {
+ return nil, err
+ }
+
+ params.Set(fieldName, s)
+ default:
+ return nil, errors.Errorf("unknown type %s", f.Kind().String())
+ }
+ }
+ return params, nil
+}
+
+// WithForce
+func (o *RemoveOptions) WithForce(value bool) *RemoveOptions {
+ v := &value
+ o.Force = v
+ return o
+}
diff --git a/pkg/bindings/images/rm.go b/pkg/bindings/images/rm.go
index 9685b75e4..0b3b88165 100644
--- a/pkg/bindings/images/rm.go
+++ b/pkg/bindings/images/rm.go
@@ -41,17 +41,19 @@ func BatchRemove(ctx context.Context, images []string, opts entities.ImageRemove
return &report.ImageRemoveReport, errorhandling.StringsToErrors(report.Errors)
}
-// Remove removes an image from the local storage. Use force to remove an
+// Remove removes an image from the local storage. Use optional force option to remove an
// image, even if it's used by containers.
-func Remove(ctx context.Context, nameOrID string, force bool) (*entities.ImageRemoveReport, error) {
+func Remove(ctx context.Context, nameOrID string, options *RemoveOptions) (*entities.ImageRemoveReport, error) {
var report handlers.LibpodImagesRemoveReport
conn, err := bindings.GetClient(ctx)
if err != nil {
return nil, err
}
- params := url.Values{}
- params.Set("force", strconv.FormatBool(force))
+ params, err := options.ToParams()
+ if err != nil {
+ return nil, err
+ }
response, err := conn.DoRequest(nil, http.MethodDelete, "/images/%s", params, nil, nameOrID)
if err != nil {
return nil, err
diff --git a/pkg/bindings/images/types.go b/pkg/bindings/images/types.go
new file mode 100644
index 000000000..340c7bdb9
--- /dev/null
+++ b/pkg/bindings/images/types.go
@@ -0,0 +1,8 @@
+package images
+
+//go:generate go run ../generator/generator.go RemoveOptions
+// RemoveOptions are optional options for image removal
+type RemoveOptions struct {
+ // Forces removes all containers based on the image
+ Force *bool
+}
diff --git a/pkg/bindings/test/images_test.go b/pkg/bindings/test/images_test.go
index 7d9415f91..684f110e8 100644
--- a/pkg/bindings/test/images_test.go
+++ b/pkg/bindings/test/images_test.go
@@ -84,7 +84,7 @@ var _ = Describe("Podman images", func() {
// Test to validate the remove image api
It("remove image", func() {
// Remove invalid image should be a 404
- response, err := images.Remove(bt.conn, "foobar5000", false)
+ response, err := images.Remove(bt.conn, "foobar5000", nil)
Expect(err).ToNot(BeNil())
Expect(response).To(BeNil())
code, _ := bindings.CheckResponseCode(err)
@@ -93,7 +93,7 @@ var _ = Describe("Podman images", func() {
// Remove an image by name, validate image is removed and error is nil
inspectData, err := images.GetImage(bt.conn, busybox.shortName, nil)
Expect(err).To(BeNil())
- response, err = images.Remove(bt.conn, busybox.shortName, false)
+ response, err = images.Remove(bt.conn, busybox.shortName, nil)
Expect(err).To(BeNil())
code, _ = bindings.CheckResponseCode(err)
@@ -113,12 +113,13 @@ var _ = Describe("Podman images", func() {
// try to remove the image "alpine". This should fail since we are not force
// deleting hence image cannot be deleted until the container is deleted.
- response, err = images.Remove(bt.conn, alpine.shortName, false)
+ response, err = images.Remove(bt.conn, alpine.shortName, nil)
code, _ = bindings.CheckResponseCode(err)
Expect(code).To(BeNumerically("==", http.StatusConflict))
// Removing the image "alpine" where force = true
- response, err = images.Remove(bt.conn, alpine.shortName, true)
+ options := images.RemoveOptions{}
+ response, err = images.Remove(bt.conn, alpine.shortName, options.WithForce(true))
Expect(err).To(BeNil())
// To be extra sure, check if the previously created container
// is gone as well.
@@ -213,7 +214,7 @@ var _ = Describe("Podman images", func() {
It("Load|Import Image", func() {
// load an image
- _, err := images.Remove(bt.conn, alpine.name, false)
+ _, err := images.Remove(bt.conn, alpine.name, nil)
Expect(err).To(BeNil())
exists, err := images.Exists(bt.conn, alpine.name)
Expect(err).To(BeNil())
@@ -231,7 +232,7 @@ var _ = Describe("Podman images", func() {
// load with a repo name
f, err = os.Open(filepath.Join(ImageCacheDir, alpine.tarballName))
Expect(err).To(BeNil())
- _, err = images.Remove(bt.conn, alpine.name, false)
+ _, err = images.Remove(bt.conn, alpine.name, nil)
Expect(err).To(BeNil())
exists, err = images.Exists(bt.conn, alpine.name)
Expect(err).To(BeNil())
@@ -247,7 +248,7 @@ var _ = Describe("Podman images", func() {
// load with a bad repo name should trigger a 500
f, err = os.Open(filepath.Join(ImageCacheDir, alpine.tarballName))
Expect(err).To(BeNil())
- _, err = images.Remove(bt.conn, alpine.name, false)
+ _, err = images.Remove(bt.conn, alpine.name, nil)
Expect(err).To(BeNil())
exists, err = images.Exists(bt.conn, alpine.name)
Expect(err).To(BeNil())
@@ -275,7 +276,7 @@ var _ = Describe("Podman images", func() {
It("Import Image", func() {
// load an image
- _, err = images.Remove(bt.conn, alpine.name, false)
+ _, err = images.Remove(bt.conn, alpine.name, nil)
Expect(err).To(BeNil())
exists, err := images.Exists(bt.conn, alpine.name)
Expect(err).To(BeNil())
diff --git a/pkg/bindings/test/manifests_test.go b/pkg/bindings/test/manifests_test.go
index 55fc4cb0d..a4ecaa20f 100644
--- a/pkg/bindings/test/manifests_test.go
+++ b/pkg/bindings/test/manifests_test.go
@@ -47,7 +47,7 @@ var _ = Describe("Podman containers ", func() {
code, _ := bindings.CheckResponseCode(err)
Expect(code).To(BeNumerically("==", http.StatusInternalServerError))
- _, err = images.Remove(bt.conn, id, false)
+ _, err = images.Remove(bt.conn, id, nil)
Expect(err).To(BeNil())
// create manifest list with images
diff --git a/pkg/domain/entities/container_ps.go b/pkg/domain/entities/container_ps.go
index b4e8446cb..ff3b087ed 100644
--- a/pkg/domain/entities/container_ps.go
+++ b/pkg/domain/entities/container_ps.go
@@ -12,6 +12,8 @@ import (
// Listcontainer describes a container suitable for listing
type ListContainer struct {
+ // AutoRemove
+ AutoRemove bool
// Container command
Command []string
// Container creation time
diff --git a/pkg/domain/entities/containers.go b/pkg/domain/entities/containers.go
index 39d679eaf..01086a2b3 100644
--- a/pkg/domain/entities/containers.go
+++ b/pkg/domain/entities/containers.go
@@ -227,8 +227,10 @@ type ContainerLogsOptions struct {
Tail int64
// Show timestamps in the logs.
Timestamps bool
- // Write the logs to Writer.
- Writer io.Writer
+ // Write the stdout to this Writer.
+ StdoutWriter io.Writer
+ // Write the stderr to this Writer.
+ StderrWriter io.Writer
}
// ExecOptions describes the cli values to exec into
diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go
index ff4277a2e..ec65dbe44 100644
--- a/pkg/domain/infra/abi/containers.go
+++ b/pkg/domain/infra/abi/containers.go
@@ -925,7 +925,7 @@ func (ic *ContainerEngine) ContainerRun(ctx context.Context, opts entities.Conta
}
func (ic *ContainerEngine) ContainerLogs(ctx context.Context, containers []string, options entities.ContainerLogsOptions) error {
- if options.Writer == nil {
+ if options.StdoutWriter == nil && options.StderrWriter == nil {
return errors.New("no io.Writer set for container logs")
}
@@ -963,7 +963,7 @@ func (ic *ContainerEngine) ContainerLogs(ctx context.Context, containers []strin
}()
for line := range logChannel {
- fmt.Fprintln(options.Writer, line.String(logOpts))
+ line.Write(options.StdoutWriter, options.StderrWriter, logOpts)
}
return nil
diff --git a/pkg/domain/infra/tunnel/containers.go b/pkg/domain/infra/tunnel/containers.go
index e65fef0a4..ad7688f62 100644
--- a/pkg/domain/infra/tunnel/containers.go
+++ b/pkg/domain/infra/tunnel/containers.go
@@ -360,11 +360,12 @@ func (ic *ContainerEngine) ContainerCreate(ctx context.Context, s *specgen.SpecG
func (ic *ContainerEngine) ContainerLogs(_ context.Context, nameOrIDs []string, options entities.ContainerLogsOptions) error {
since := options.Since.Format(time.RFC3339)
tail := strconv.FormatInt(options.Tail, 10)
- stdout := options.Writer != nil
+ stdout := options.StdoutWriter != nil
+ stderr := options.StderrWriter != nil
opts := containers.LogOptions{
Follow: &options.Follow,
Since: &since,
- Stderr: &stdout,
+ Stderr: &stderr,
Stdout: &stdout,
Tail: &tail,
Timestamps: &options.Timestamps,
@@ -372,10 +373,11 @@ func (ic *ContainerEngine) ContainerLogs(_ context.Context, nameOrIDs []string,
}
var err error
- outCh := make(chan string)
+ stdoutCh := make(chan string)
+ stderrCh := make(chan string)
ctx, cancel := context.WithCancel(context.Background())
go func() {
- err = containers.Logs(ic.ClientCxt, nameOrIDs[0], opts, outCh, outCh)
+ err = containers.Logs(ic.ClientCxt, nameOrIDs[0], opts, stdoutCh, stderrCh)
cancel()
}()
@@ -383,8 +385,14 @@ func (ic *ContainerEngine) ContainerLogs(_ context.Context, nameOrIDs []string,
select {
case <-ctx.Done():
return err
- case line := <-outCh:
- _, _ = io.WriteString(options.Writer, line+"\n")
+ case line := <-stdoutCh:
+ if options.StdoutWriter != nil {
+ _, _ = io.WriteString(options.StdoutWriter, line+"\n")
+ }
+ case line := <-stderrCh:
+ if options.StderrWriter != nil {
+ _, _ = io.WriteString(options.StderrWriter, line+"\n")
+ }
}
}
}
@@ -515,6 +523,29 @@ func (ic *ContainerEngine) ContainerStart(ctx context.Context, namesOrIds []stri
reports = append(reports, &report)
return reports, errors.Wrapf(report.Err, "unable to start container %s", name)
}
+ if ctr.AutoRemove {
+ // Defer the removal, so we can return early if needed and
+ // de-spaghetti the code.
+ defer func() {
+ shouldRestart, err := containers.ShouldRestart(ic.ClientCxt, ctr.ID)
+ if err != nil {
+ logrus.Errorf("Failed to check if %s should restart: %v", ctr.ID, err)
+ return
+ }
+
+ if !shouldRestart {
+ if err := containers.Remove(ic.ClientCxt, ctr.ID, bindings.PFalse, bindings.PTrue); err != nil {
+ if errorhandling.Contains(err, define.ErrNoSuchCtr) ||
+ errorhandling.Contains(err, define.ErrCtrRemoved) {
+ logrus.Warnf("Container %s does not exist: %v", ctr.ID, err)
+ } else {
+ logrus.Errorf("Error removing container %s: %v", ctr.ID, err)
+ }
+ }
+ }
+ }()
+ }
+
exitCode, err := containers.Wait(ic.ClientCxt, name, nil)
if err == define.ErrNoSuchCtr {
// Check events
@@ -535,6 +566,16 @@ func (ic *ContainerEngine) ContainerStart(ctx context.Context, namesOrIds []stri
if !ctrRunning {
err = containers.Start(ic.ClientCxt, name, &options.DetachKeys)
if err != nil {
+ if ctr.AutoRemove {
+ if err := containers.Remove(ic.ClientCxt, ctr.ID, bindings.PFalse, bindings.PTrue); err != nil {
+ if errorhandling.Contains(err, define.ErrNoSuchCtr) ||
+ errorhandling.Contains(err, define.ErrCtrRemoved) {
+ logrus.Warnf("Container %s does not exist: %v", ctr.ID, err)
+ } else {
+ logrus.Errorf("Error removing container %s: %v", ctr.ID, err)
+ }
+ }
+ }
report.Err = errors.Wrapf(err, "unable to start container %q", name)
report.ExitCode = define.ExitCode(err)
reports = append(reports, &report)
diff --git a/pkg/ps/ps.go b/pkg/ps/ps.go
index cfdf3ee49..6c26e8708 100644
--- a/pkg/ps/ps.go
+++ b/pkg/ps/ps.go
@@ -179,24 +179,25 @@ func ListContainerBatch(rt *libpod.Runtime, ctr *libpod.Container, opts entities
}
ps := entities.ListContainer{
- Command: conConfig.Command,
- Created: conConfig.CreatedTime,
- Exited: exited,
- ExitCode: exitCode,
- ExitedAt: exitedTime.Unix(),
- ID: conConfig.ID,
- Image: conConfig.RootfsImageName,
- ImageID: conConfig.RootfsImageID,
- IsInfra: conConfig.IsInfra,
- Labels: conConfig.Labels,
- Mounts: ctr.UserVolumes(),
- Names: []string{conConfig.Name},
- Pid: pid,
- Pod: conConfig.Pod,
- Ports: portMappings,
- Size: size,
- StartedAt: startedTime.Unix(),
- State: conState.String(),
+ AutoRemove: ctr.AutoRemove(),
+ Command: conConfig.Command,
+ Created: conConfig.CreatedTime,
+ Exited: exited,
+ ExitCode: exitCode,
+ ExitedAt: exitedTime.Unix(),
+ ID: conConfig.ID,
+ Image: conConfig.RootfsImageName,
+ ImageID: conConfig.RootfsImageID,
+ IsInfra: conConfig.IsInfra,
+ Labels: conConfig.Labels,
+ Mounts: ctr.UserVolumes(),
+ Names: []string{conConfig.Name},
+ Pid: pid,
+ Pod: conConfig.Pod,
+ Ports: portMappings,
+ Size: size,
+ StartedAt: startedTime.Unix(),
+ State: conState.String(),
}
if opts.Pod && len(conConfig.Pod) > 0 {
podName, err := rt.GetName(conConfig.Pod)
diff --git a/test/apiv2/20-containers.at b/test/apiv2/20-containers.at
index 5c35edf2b..bc6efc20d 100644
--- a/test/apiv2/20-containers.at
+++ b/test/apiv2/20-containers.at
@@ -5,8 +5,10 @@
# WORKDIR=/data
ENV_WORKDIR_IMG=quay.io/libpod/testimage:20200929
+MultiTagName=localhost/test/testformultitag:tag
podman pull $IMAGE &>/dev/null
+podman tag $IMAGE $MultiTagName
podman pull $ENV_WORKDIR_IMG &>/dev/null
# Unimplemented
#t POST libpod/containers/create '' 201 'sdf'
@@ -216,4 +218,13 @@ t GET containers/$cid/json 200 \
t DELETE containers/$cid 204
+# when the image had multi tags, the container's Image should be correct
+# Fixes https://github.com/containers/podman/issues/8547
+t POST containers/create Image=${MultiTagName} 201 \
+ .Id~[0-9a-f]\\{64\\}
+cid=$(jq -r '.Id' <<<"$output")
+t GET containers/$cid/json 200 \
+ .Image=${MultiTagName}
+t DELETE containers/$cid 204
+t DELETE images/${MultiTagName}?force=true 200
# vim: filetype=sh
diff --git a/test/apiv2/35-networks.at b/test/apiv2/35-networks.at
index 0ce56ee3c..5327bd076 100644
--- a/test/apiv2/35-networks.at
+++ b/test/apiv2/35-networks.at
@@ -50,7 +50,13 @@ t GET networks?filters=%7B%22dangling%22%3A%5B%221%22%5D%7D 500 \
# network inspect docker
t GET networks/a7662f44d65029fd4635c91feea3d720a57cef52e2a9fcc7772b69072cc1ccd1 200 \
.Name=network1 \
-.Id=a7662f44d65029fd4635c91feea3d720a57cef52e2a9fcc7772b69072cc1ccd1
+.Id=a7662f44d65029fd4635c91feea3d720a57cef52e2a9fcc7772b69072cc1ccd1 \
+.Scope=local
+
+# network create docker
+t POST networks/create '"Name":"net3","IPAM":{"Config":[]}' 201
+# network delete docker
+t DELETE networks/net3 204
# clean the network
t DELETE libpod/networks/network1 200 \
diff --git a/test/e2e/logs_test.go b/test/e2e/logs_test.go
index a749a86ff..aae6d4f02 100644
--- a/test/e2e/logs_test.go
+++ b/test/e2e/logs_test.go
@@ -355,4 +355,21 @@ var _ = Describe("Podman logs", func() {
Expect(outlines[0]).To(Equal("1\r"))
Expect(outlines[1]).To(Equal("2\r"))
})
+
+ It("podman logs test stdout and stderr", func() {
+ cname := "log-test"
+ logc := podmanTest.Podman([]string{"run", "--name", cname, ALPINE, "sh", "-c", "echo stdout; echo stderr >&2"})
+ logc.WaitWithDefaultTimeout()
+ Expect(logc).To(Exit(0))
+
+ wait := podmanTest.Podman([]string{"wait", cname})
+ wait.WaitWithDefaultTimeout()
+ Expect(wait).To(Exit(0))
+
+ results := podmanTest.Podman([]string{"logs", cname})
+ results.WaitWithDefaultTimeout()
+ Expect(results).To(Exit(0))
+ Expect(results.OutputToString()).To(Equal("stdout"))
+ Expect(results.ErrorToString()).To(Equal("stderr"))
+ })
})
diff --git a/test/e2e/play_kube_test.go b/test/e2e/play_kube_test.go
index 5ecfdd6b5..3a2387559 100644
--- a/test/e2e/play_kube_test.go
+++ b/test/e2e/play_kube_test.go
@@ -1072,7 +1072,7 @@ var _ = Describe("Podman play kube", func() {
logs := podmanTest.Podman([]string{"logs", getCtrNameInPod(pod)})
logs.WaitWithDefaultTimeout()
Expect(logs.ExitCode()).To(Equal(0))
- Expect(logs.OutputToString()).To(ContainSubstring("Operation not permitted"))
+ Expect(logs.ErrorToString()).To(ContainSubstring("Operation not permitted"))
})
It("podman play kube seccomp pod level", func() {
@@ -1099,7 +1099,7 @@ var _ = Describe("Podman play kube", func() {
logs := podmanTest.Podman([]string{"logs", getCtrNameInPod(pod)})
logs.WaitWithDefaultTimeout()
Expect(logs.ExitCode()).To(Equal(0))
- Expect(logs.OutputToString()).To(ContainSubstring("Operation not permitted"))
+ Expect(logs.ErrorToString()).To(ContainSubstring("Operation not permitted"))
})
It("podman play kube with pull policy of never should be 125", func() {
diff --git a/test/e2e/start_test.go b/test/e2e/start_test.go
index 942e00123..a6f22e007 100644
--- a/test/e2e/start_test.go
+++ b/test/e2e/start_test.go
@@ -49,6 +49,29 @@ var _ = Describe("Podman start", func() {
Expect(session.ExitCode()).To(Equal(0))
})
+ It("podman start --rm removed on failure", func() {
+ session := podmanTest.Podman([]string{"create", "--name=test", "--rm", ALPINE, "foo"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ session = podmanTest.Podman([]string{"start", "test"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(125))
+ session = podmanTest.Podman([]string{"container", "exists", "test"})
+ Expect(session.ExitCode()).To(Not(Equal(0)))
+ })
+
+ It("podman start --rm --attach removed on failure", func() {
+ session := podmanTest.Podman([]string{"create", "--rm", ALPINE, "foo"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ cid := session.OutputToString()
+ session = podmanTest.Podman([]string{"start", "--attach", cid})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(125))
+ session = podmanTest.Podman([]string{"container", "exists", cid})
+ Expect(session.ExitCode()).To(Not(Equal(0)))
+ })
+
It("podman container start single container by id", func() {
session := podmanTest.Podman([]string{"container", "create", ALPINE, "ls"})
session.WaitWithDefaultTimeout()
diff --git a/test/e2e/toolbox_test.go b/test/e2e/toolbox_test.go
index 7393b13cb..6f04ce48c 100644
--- a/test/e2e/toolbox_test.go
+++ b/test/e2e/toolbox_test.go
@@ -239,7 +239,7 @@ var _ = Describe("Toolbox-specific testing", func() {
session = podmanTest.Podman([]string{"logs", "test"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
- Expect(session.OutputToString()).To(ContainSubstring(expectedOutput))
+ Expect(session.ErrorToString()).To(ContainSubstring(expectedOutput))
})
It("podman create --userns=keep-id + podman exec - adding group with groupadd", func() {