aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--libpod/container_internal_linux.go20
-rw-r--r--libpod/container_log.go54
-rw-r--r--pkg/api/handlers/compat/images.go34
-rw-r--r--pkg/api/handlers/compat/networks.go2
-rw-r--r--pkg/api/handlers/compat/swagger.go7
-rw-r--r--pkg/api/handlers/libpod/images_pull.go2
-rw-r--r--pkg/api/handlers/libpod/networks.go3
-rw-r--r--pkg/api/handlers/libpod/swagger.go7
-rw-r--r--pkg/api/server/register_images.go20
-rw-r--r--pkg/api/server/register_networks.go9
-rw-r--r--test/apiv2/python/rest_api/test_v2_0_0_image.py5
-rw-r--r--test/e2e/logs_test.go14
12 files changed, 120 insertions, 57 deletions
diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go
index a3acc3198..94bf7855b 100644
--- a/libpod/container_internal_linux.go
+++ b/libpod/container_internal_linux.go
@@ -1650,22 +1650,20 @@ func (c *Container) generateResolvConf() (string, error) {
}
}
- // Determine the endpoint for resolv.conf in case it is a symlink
- resolvPath, err := filepath.EvalSymlinks(resolvConf)
+ contents, err := ioutil.ReadFile(resolvConf)
// resolv.conf doesn't have to exists
if err != nil && !os.IsNotExist(err) {
return "", err
}
- // Determine if symlink points to any of the systemd-resolved files
- if strings.HasPrefix(resolvPath, "/run/systemd/resolve/") {
- resolvPath = "/run/systemd/resolve/resolv.conf"
- }
-
- contents, err := ioutil.ReadFile(resolvPath)
- // resolv.conf doesn't have to exists
- if err != nil && !os.IsNotExist(err) {
- return "", err
+ ns := resolvconf.GetNameservers(contents)
+ // check if systemd-resolved is used, assume it is used when 127.0.0.53 is the only nameserver
+ if len(ns) == 1 && ns[0] == "127.0.0.53" {
+ // read the actual resolv.conf file for systemd-resolved
+ contents, err = ioutil.ReadFile("/run/systemd/resolve/resolv.conf")
+ if err != nil {
+ return "", errors.Wrapf(err, "detected that systemd-resolved is in use, but could not locate real resolv.conf")
+ }
}
ipv6 := false
diff --git a/libpod/container_log.go b/libpod/container_log.go
index c207df819..a30e4f5cc 100644
--- a/libpod/container_log.go
+++ b/libpod/container_log.go
@@ -4,11 +4,10 @@ import (
"context"
"fmt"
"os"
- "time"
"github.com/containers/podman/v3/libpod/define"
+ "github.com/containers/podman/v3/libpod/events"
"github.com/containers/podman/v3/libpod/logs"
- "github.com/hpcloud/tail/watch"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
@@ -94,27 +93,40 @@ func (c *Container) readFromLogFile(ctx context.Context, options *logs.LogOption
}()
// Check if container is still running or paused
if options.Follow {
+ state, err := c.State()
+ if err != nil || state != define.ContainerStateRunning {
+ // If the container isn't running or if we encountered
+ // an error getting its state, instruct the logger to
+ // read the file until EOF.
+ tailError := t.StopAtEOF()
+ if tailError != nil && fmt.Sprintf("%v", tailError) != "tail: stop at eof" {
+ logrus.Error(tailError)
+ }
+ if errors.Cause(err) != define.ErrNoSuchCtr {
+ logrus.Error(err)
+ }
+ return nil
+ }
+
+ // The container is running, so we need to wait until the container exited
go func() {
- for {
- state, err := c.State()
- time.Sleep(watch.POLL_DURATION)
- if err != nil {
- tailError := t.StopAtEOF()
- if tailError != nil && fmt.Sprintf("%v", tailError) != "tail: stop at eof" {
- logrus.Error(tailError)
- }
- if errors.Cause(err) != define.ErrNoSuchCtr {
- logrus.Error(err)
- }
- break
- }
- if state != define.ContainerStateRunning && state != define.ContainerStatePaused {
- tailError := t.StopAtEOF()
- if tailError != nil && fmt.Sprintf("%v", tailError) != "tail: stop at eof" {
- logrus.Error(tailError)
- }
- break
+ eventChannel := make(chan *events.Event)
+ eventOptions := events.ReadOptions{
+ EventChannel: eventChannel,
+ Filters: []string{"event=died", "container=" + c.ID()},
+ Stream: true,
+ }
+ go func() {
+ if err := c.runtime.Events(ctx, eventOptions); err != nil {
+ logrus.Errorf("Error waiting for container to exit: %v", err)
}
+ }()
+ // Now wait for the died event and signal to finish
+ // reading the log until EOF.
+ <-eventChannel
+ tailError := t.StopAtEOF()
+ if tailError != nil && fmt.Sprintf("%v", tailError) != "tail: stop at eof" {
+ logrus.Error(tailError)
}
}()
}
diff --git a/pkg/api/handlers/compat/images.go b/pkg/api/handlers/compat/images.go
index 7b336c470..ac212474b 100644
--- a/pkg/api/handlers/compat/images.go
+++ b/pkg/api/handlers/compat/images.go
@@ -168,6 +168,8 @@ func CreateImageFromSrc(w http.ResponseWriter, r *http.Request) {
query := struct {
FromSrc string `schema:"fromSrc"`
Changes []string `schema:"changes"`
+ Message string `schema:"message"`
+ Repo string `shchema:"repo"`
}{
// This is where you can override the golang default value for one of fields
}
@@ -184,14 +186,15 @@ func CreateImageFromSrc(w http.ResponseWriter, r *http.Request) {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to create tempfile"))
return
}
+
source = f.Name()
if err := SaveFromBody(f, r); err != nil {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to write temporary file"))
}
}
-
imageEngine := abi.ImageEngine{Libpod: runtime}
- report, err := imageEngine.Import(r.Context(), entities.ImageImportOptions{Source: source, Changes: query.Changes})
+ // TODO: add support for ImageImportOptions to take a platform parameter. Also import https://github.com/opencontainers/image-spec/tree/master/specs-go/v1 either here or within imageEngine.Import to get default platform
+ report, err := imageEngine.Import(r.Context(), entities.ImageImportOptions{Source: source, Changes: query.Changes, Message: query.Message, Reference: query.Repo})
if err != nil {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to import tarball"))
return
@@ -224,10 +227,10 @@ func CreateImageFromImage(w http.ResponseWriter, r *http.Request) {
query := struct {
FromImage string `schema:"fromImage"`
Tag string `schema:"tag"`
+ Platform string `schema:"platform"`
}{
// This is where you can override the golang default value for one of fields
}
-
if err := decoder.Decode(&query, r.URL.Query()); err != nil {
utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String()))
return
@@ -250,12 +253,36 @@ func CreateImageFromImage(w http.ResponseWriter, r *http.Request) {
}
defer auth.RemoveAuthfile(authfile)
+ platformSpecs := strings.Split(query.Platform, "/") // split query into its parts
+
+ addOS := true // default assume true due to structure of if/else below
+ addArch := false
+ addVariant := false
+
+ if len(platformSpecs) > 1 { // if we have two arguments then we have os and arch
+ addArch = true
+ if len(platformSpecs) > 2 { // if we have 3 arguments then we have os arch and variant
+ addVariant = true
+ }
+ } else if len(platformSpecs) == 0 {
+ addOS = false
+ }
+
pullOptions := &libimage.PullOptions{}
pullOptions.AuthFilePath = authfile
if authConf != nil {
pullOptions.Username = authConf.Username
pullOptions.Password = authConf.Password
pullOptions.IdentityToken = authConf.IdentityToken
+ if addOS { // if the len is not 0
+ pullOptions.OS = platformSpecs[0]
+ if addArch {
+ pullOptions.Architecture = platformSpecs[1]
+ }
+ if addVariant {
+ pullOptions.Variant = platformSpecs[2]
+ }
+ }
}
pullOptions.Writer = os.Stderr // allows for debugging on the server
@@ -294,7 +321,6 @@ loop: // break out of for/select infinite loop
Error string `json:"error,omitempty"`
Id string `json:"id,omitempty"` // nolint
}
-
select {
case e := <-progress:
switch e.Event {
diff --git a/pkg/api/handlers/compat/networks.go b/pkg/api/handlers/compat/networks.go
index 77ed548d8..04f8570ff 100644
--- a/pkg/api/handlers/compat/networks.go
+++ b/pkg/api/handlers/compat/networks.go
@@ -414,7 +414,7 @@ func Prune(w http.ResponseWriter, r *http.Request) {
type response struct {
NetworksDeleted []string
}
- var prunedNetworks []string //nolint
+ prunedNetworks := []string{}
for _, pr := range pruneReports {
if pr.Error != nil {
logrus.Error(pr.Error)
diff --git a/pkg/api/handlers/compat/swagger.go b/pkg/api/handlers/compat/swagger.go
index a0783e723..b773799ef 100644
--- a/pkg/api/handlers/compat/swagger.go
+++ b/pkg/api/handlers/compat/swagger.go
@@ -77,10 +77,3 @@ type swagCompatNetworkDisconnectRequest struct {
// in:body
Body struct{ types.NetworkDisconnect }
}
-
-// Network prune
-// swagger:response NetworkPruneResponse
-type swagCompatNetworkPruneResponse struct {
- // in:body
- Body []string
-}
diff --git a/pkg/api/handlers/libpod/images_pull.go b/pkg/api/handlers/libpod/images_pull.go
index fe56aa31d..e88b53a4b 100644
--- a/pkg/api/handlers/libpod/images_pull.go
+++ b/pkg/api/handlers/libpod/images_pull.go
@@ -85,7 +85,7 @@ func ImagesPull(w http.ResponseWriter, r *http.Request) {
var pulledImages []*libimage.Image
var pullError error
- runCtx, cancel := context.WithCancel(context.Background())
+ runCtx, cancel := context.WithCancel(r.Context())
go func() {
defer cancel()
pulledImages, pullError = runtime.LibimageRuntime().Pull(runCtx, query.Reference, config.PullPolicyAlways, pullOptions)
diff --git a/pkg/api/handlers/libpod/networks.go b/pkg/api/handlers/libpod/networks.go
index 5417f778e..e4f450e12 100644
--- a/pkg/api/handlers/libpod/networks.go
+++ b/pkg/api/handlers/libpod/networks.go
@@ -190,5 +190,8 @@ func Prune(w http.ResponseWriter, r *http.Request) {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err)
return
}
+ if pruneReports == nil {
+ pruneReports = []*entities.NetworkPruneReport{}
+ }
utils.WriteResponse(w, http.StatusOK, pruneReports)
}
diff --git a/pkg/api/handlers/libpod/swagger.go b/pkg/api/handlers/libpod/swagger.go
index 19eced986..2ac5009fc 100644
--- a/pkg/api/handlers/libpod/swagger.go
+++ b/pkg/api/handlers/libpod/swagger.go
@@ -119,6 +119,13 @@ type swagNetworkCreateReport struct {
Body entities.NetworkCreateReport
}
+// Network prune
+// swagger:response NetworkPruneResponse
+type swagNetworkPruneResponse struct {
+ // in:body
+ Body []entities.NetworkPruneReport
+}
+
func ServeSwagger(w http.ResponseWriter, r *http.Request) {
path := DefaultPodmanSwaggerSpec
if p, found := os.LookupEnv("PODMAN_SWAGGER_SPEC"); found {
diff --git a/pkg/api/server/register_images.go b/pkg/api/server/register_images.go
index 9634bd83b..b28818768 100644
--- a/pkg/api/server/register_images.go
+++ b/pkg/api/server/register_images.go
@@ -28,15 +28,28 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error {
// - in: query
// name: fromImage
// type: string
- // description: needs description
+ // description: Name of the image to pull. The name may include a tag or digest. This parameter may only be used when pulling an image. The pull is cancelled if the HTTP connection is closed.
// - in: query
// name: fromSrc
// type: string
- // description: needs description
+ // description: Source to import. The value may be a URL from which the image can be retrieved or - to read the image from the request body. This parameter may only be used when importing an image
+ // - in: query
+ // name: repo
+ // type: string
+ // description: Repository name given to an image when it is imported. The repo may include a tag. This parameter may only be used when importing an image.
// - in: query
// name: tag
// type: string
- // description: needs description
+ // description: Tag or digest. If empty when pulling an image, this causes all tags for the given image to be pulled.
+ // - in: query
+ // name: message
+ // type: string
+ // description: Set commit message for imported image.
+ // - in: query
+ // name: platform
+ // type: string
+ // description: Platform in the format os[/arch[/variant]]
+ // default: ""
// - in: header
// name: X-Registry-Auth
// type: string
@@ -45,6 +58,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error {
// name: request
// schema:
// type: string
+ // format: binary
// description: Image content if fromSrc parameter was used
// responses:
// 200:
diff --git a/pkg/api/server/register_networks.go b/pkg/api/server/register_networks.go
index 4d9806316..d122c6a36 100644
--- a/pkg/api/server/register_networks.go
+++ b/pkg/api/server/register_networks.go
@@ -180,9 +180,12 @@ func (s *APIServer) registerNetworkHandlers(r *mux.Router) error {
// 200:
// description: OK
// schema:
- // type: array
- // items:
- // type: string
+ // type: object
+ // properties:
+ // NetworksDeleted:
+ // type: array
+ // items:
+ // type: string
// 500:
// $ref: "#/responses/InternalError"
r.HandleFunc(VersionedPath("/networks/prune"), s.APIHandler(compat.Prune)).Methods(http.MethodPost)
diff --git a/test/apiv2/python/rest_api/test_v2_0_0_image.py b/test/apiv2/python/rest_api/test_v2_0_0_image.py
index 243b1d5f5..2cd7bfa96 100644
--- a/test/apiv2/python/rest_api/test_v2_0_0_image.py
+++ b/test/apiv2/python/rest_api/test_v2_0_0_image.py
@@ -86,6 +86,11 @@ class ImageTestCase(APITestCase):
self.assertTrue(keys["id"], "Expected to find id stanza")
self.assertTrue(keys["images"], "Expected to find images stanza")
self.assertTrue(keys["stream"], "Expected to find stream progress stanza's")
+ def test_create(self):
+ r = requests.post(self.podman_url + "/v1.40/images/create?fromImage=alpine&platform=linux/amd64/v8", timeout=15)
+ self.assertEqual(r.status_code, 200, r.text)
+ r = requests.post(self.podman_url + "/v1.40/images/create?fromSrc=-&repo=fedora&message=testing123", timeout=15)
+ self.assertEqual(r.status_code, 200, r.text)
def test_search_compat(self):
url = self.podman_url + "/v1.40/images/search"
diff --git a/test/e2e/logs_test.go b/test/e2e/logs_test.go
index 4d9cbb48b..b576fa072 100644
--- a/test/e2e/logs_test.go
+++ b/test/e2e/logs_test.go
@@ -173,9 +173,9 @@ var _ = Describe("Podman logs", func() {
})
It("streaming output: "+log, func() {
- containerName := "logs-f-rm"
+ containerName := "logs-f"
- logc := podmanTest.Podman([]string{"run", "--log-driver", log, "--rm", "--name", containerName, "-dt", ALPINE, "sh", "-c", "echo podman; sleep 1; echo podman"})
+ logc := podmanTest.Podman([]string{"run", "--log-driver", log, "--name", containerName, "-dt", ALPINE, "sh", "-c", "echo podman-1; sleep 1; echo podman-2"})
logc.WaitWithDefaultTimeout()
Expect(logc).To(Exit(0))
@@ -183,10 +183,8 @@ var _ = Describe("Podman logs", func() {
results.WaitWithDefaultTimeout()
Expect(results).To(Exit(0))
- // TODO: we should actually check for two podman lines,
- // but as of 2020-06-17 there's a race condition in which
- // 'logs -f' may not catch all output from a container
- Expect(results.OutputToString()).To(ContainSubstring("podman"))
+ Expect(results.OutputToString()).To(ContainSubstring("podman-1"))
+ Expect(results.OutputToString()).To(ContainSubstring("podman-2"))
// Container should now be terminatING or terminatED, but we
// have no guarantee of which: 'logs -f' does not necessarily
@@ -199,6 +197,10 @@ var _ = Describe("Podman logs", func() {
} else {
Expect(inspect.ErrorToString()).To(ContainSubstring("no such container"))
}
+
+ results = podmanTest.Podman([]string{"rm", "-f", containerName})
+ results.WaitWithDefaultTimeout()
+ Expect(results).To(Exit(0))
})
It("follow output stopped container: "+log, func() {