diff options
-rw-r--r-- | Makefile | 2 | ||||
-rw-r--r-- | libpod/image/image.go | 72 | ||||
-rw-r--r-- | pkg/api/handlers/compat/containers_attach.go | 6 | ||||
-rw-r--r-- | pkg/api/handlers/compat/exec.go | 6 | ||||
-rw-r--r-- | pkg/api/handlers/compat/images_build.go | 40 | ||||
-rw-r--r-- | pkg/api/server/idle/tracker.go | 96 | ||||
-rw-r--r-- | pkg/api/server/idletracker/idletracker.go | 74 | ||||
-rw-r--r-- | pkg/api/server/register_images.go | 7 | ||||
-rw-r--r-- | pkg/api/server/server.go | 22 | ||||
-rw-r--r-- | pkg/bindings/images/build.go | 5 | ||||
-rw-r--r-- | pkg/registries/registries.go | 5 | ||||
-rw-r--r-- | pkg/specgen/generate/container.go | 39 | ||||
-rw-r--r-- | pkg/specgen/generate/security.go | 3 | ||||
-rw-r--r-- | test/e2e/cp_test.go | 4 | ||||
-rw-r--r-- | test/e2e/create_test.go | 17 | ||||
-rw-r--r-- | test/e2e/network_create_test.go | 2 | ||||
-rw-r--r-- | test/e2e/network_test.go | 4 | ||||
-rw-r--r-- | test/e2e/pull_test.go | 100 | ||||
-rw-r--r-- | test/registries.conf | 1 | ||||
-rw-r--r-- | test/system/030-run.bats | 4 | ||||
-rw-r--r-- | test/system/075-exec.bats | 4 |
21 files changed, 375 insertions, 138 deletions
@@ -643,7 +643,7 @@ install.libseccomp.sudo: pkg/varlink/iopodman.go: .gopathok pkg/varlink/io.podman.varlink ifneq (,$(findstring Linux,$(shell uname -s))) # Only generate the varlink code on Linux (see issue #4814). - GO111MODULE=off $(GO) generate ./pkg/varlink/... + $(GO) generate ./pkg/varlink/... endif API.md: pkg/varlink/io.podman.varlink diff --git a/libpod/image/image.go b/libpod/image/image.go index 5dfb33afb..f5bf47694 100644 --- a/libpod/image/image.go +++ b/libpod/image/image.go @@ -410,48 +410,92 @@ func (ir *Runtime) getLocalImage(inputName string) (string, *storage.Image, erro if inputName == "" { return "", nil, errors.Errorf("input name is blank") } + // Check if the input name has a transport and if so strip it dest, err := alltransports.ParseImageName(inputName) if err == nil && dest.DockerReference() != nil { inputName = dest.DockerReference().String() } - img, err := ir.getImage(stripSha256(inputName)) + // Early check for fully-qualified images and (short) IDs. + img, err := ir.store.Image(stripSha256(inputName)) if err == nil { - return inputName, img, err + return inputName, img, nil } - // container-storage wasn't able to find it in its current form - // check if the input name has a tag, and if not, run it through - // again + // Note that it's crucial to first decompose the image and check if + // it's a fully-qualified one or a "short name". The latter requires + // some normalization with search registries and the + // "localhost/prefix". decomposedImage, err := decompose(inputName) if err != nil { + // We may have a storage reference. We can't parse it to a + // reference before. Otherwise, we'd normalize "alpine" to + // "docker.io/library/alpine:latest" which would break the + // order in which we should query local images below. + if ref, err := is.Transport.ParseStoreReference(ir.store, inputName); err == nil { + img, err = is.Transport.GetStoreImage(ir.store, ref) + if err == nil { + return inputName, img, nil + } + } return "", nil, err } - // The image has a registry name in it and we made sure we looked for it locally - // with a tag. It cannot be local. + // The specified image is fully qualified, so it doesn't exist in the + // storage. if decomposedImage.hasRegistry { + // However ... we may still need to normalize to docker.io: + // `docker.io/foo` -> `docker.io/library/foo` + if ref, err := is.Transport.ParseStoreReference(ir.store, inputName); err == nil { + img, err = is.Transport.GetStoreImage(ir.store, ref) + if err == nil { + return inputName, img, nil + } + } return "", nil, errors.Wrapf(ErrNoSuchImage, imageError) } - // if the image is saved with the repository localhost, searching with localhost prepended is necessary - // We don't need to strip the sha because we have already determined it is not an ID - ref, err := decomposedImage.referenceWithRegistry(DefaultLocalRegistry) + + // "Short-name image", so let's try out certain prefixes: + // 1) DefaultLocalRegistry (i.e., "localhost/) + // 2) Unqualified-search registries from registries.conf + unqualifiedSearchRegistries, err := registries.GetRegistries() if err != nil { return "", nil, err } - img, err = ir.getImage(ref.String()) + + for _, candidate := range append([]string{DefaultLocalRegistry}, unqualifiedSearchRegistries...) { + ref, err := decomposedImage.referenceWithRegistry(candidate) + if err != nil { + return "", nil, err + } + img, err := ir.store.Image(ref.String()) + if err == nil { + return ref.String(), img, nil + } + } + + // Backwards compat: normalize to docker.io as some users may very well + // rely on that. + ref, err := is.Transport.ParseStoreReference(ir.store, inputName) if err == nil { - return inputName, img, err + img, err = is.Transport.GetStoreImage(ir.store, ref) + if err == nil { + return inputName, img, nil + } } - // grab all the local images + // Last resort: look at the repotags of all images and try to find a + // match. images, err := ir.GetImages() if err != nil { return "", nil, err } - // check the repotags of all images for a match + decomposedImage, err = decompose(inputName) + if err != nil { + return "", nil, err + } repoImage, err := findImageInRepotags(decomposedImage, images) if err == nil { return inputName, repoImage, nil diff --git a/pkg/api/handlers/compat/containers_attach.go b/pkg/api/handlers/compat/containers_attach.go index e20d48d86..4a1196c89 100644 --- a/pkg/api/handlers/compat/containers_attach.go +++ b/pkg/api/handlers/compat/containers_attach.go @@ -6,7 +6,7 @@ import ( "github.com/containers/podman/v2/libpod" "github.com/containers/podman/v2/libpod/define" "github.com/containers/podman/v2/pkg/api/handlers/utils" - "github.com/containers/podman/v2/pkg/api/server/idletracker" + "github.com/containers/podman/v2/pkg/api/server/idle" "github.com/gorilla/schema" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -92,7 +92,7 @@ func AttachContainer(w http.ResponseWriter, r *http.Request) { return } - idleTracker := r.Context().Value("idletracker").(*idletracker.IdleTracker) + idleTracker := r.Context().Value("idletracker").(*idle.Tracker) hijackChan := make(chan bool, 1) // Perform HTTP attach. @@ -109,7 +109,7 @@ func AttachContainer(w http.ResponseWriter, r *http.Request) { // We do need to tell the idle tracker that the // connection has been closed, though. We can guarantee // that is true after HTTPAttach exits. - idleTracker.TrackHijackedClosed() + idleTracker.Close() } else { // A hijack was not successfully completed. We need to // report the error normally. diff --git a/pkg/api/handlers/compat/exec.go b/pkg/api/handlers/compat/exec.go index 1db950f85..df51293c2 100644 --- a/pkg/api/handlers/compat/exec.go +++ b/pkg/api/handlers/compat/exec.go @@ -10,7 +10,7 @@ import ( "github.com/containers/podman/v2/libpod/define" "github.com/containers/podman/v2/pkg/api/handlers" "github.com/containers/podman/v2/pkg/api/handlers/utils" - "github.com/containers/podman/v2/pkg/api/server/idletracker" + "github.com/containers/podman/v2/pkg/api/server/idle" "github.com/containers/podman/v2/pkg/specgen/generate" "github.com/gorilla/mux" "github.com/pkg/errors" @@ -174,7 +174,7 @@ func ExecStartHandler(w http.ResponseWriter, r *http.Request) { return } - idleTracker := r.Context().Value("idletracker").(*idletracker.IdleTracker) + idleTracker := r.Context().Value("idletracker").(*idle.Tracker) hijackChan := make(chan bool, 1) if err := sessionCtr.ExecHTTPStartAndAttach(sessionID, r, w, nil, nil, nil, hijackChan); err != nil { @@ -186,7 +186,7 @@ func ExecStartHandler(w http.ResponseWriter, r *http.Request) { // We do need to tell the idle tracker that the // connection has been closed, though. We can guarantee // that is true after HTTPAttach exits. - idleTracker.TrackHijackedClosed() + idleTracker.Close() } else { // A hijack was not successfully completed. We need to // report the error normally. diff --git a/pkg/api/handlers/compat/images_build.go b/pkg/api/handlers/compat/images_build.go index fbaf8d10a..cd10ac6ba 100644 --- a/pkg/api/handlers/compat/images_build.go +++ b/pkg/api/handlers/compat/images_build.go @@ -70,37 +70,38 @@ func BuildImage(w http.ResponseWriter, r *http.Request) { }() query := struct { + BuildArgs string `schema:"buildargs"` + CacheFrom string `schema:"cachefrom"` + CpuPeriod uint64 `schema:"cpuperiod"` // nolint + CpuQuota int64 `schema:"cpuquota"` // nolint + CpuSetCpus string `schema:"cpusetcpus"` // nolint + CpuShares uint64 `schema:"cpushares"` // nolint Dockerfile string `schema:"dockerfile"` - Tag []string `schema:"t"` ExtraHosts string `schema:"extrahosts"` - Remote string `schema:"remote"` - Quiet bool `schema:"q"` + ForceRm bool `schema:"forcerm"` + HTTPProxy bool `schema:"httpproxy"` + Labels string `schema:"labels"` + MemSwap int64 `schema:"memswap"` + Memory int64 `schema:"memory"` + NetworkMode string `schema:"networkmode"` NoCache bool `schema:"nocache"` - CacheFrom string `schema:"cachefrom"` + Outputs string `schema:"outputs"` + Platform string `schema:"platform"` Pull bool `schema:"pull"` + Quiet bool `schema:"q"` + Registry string `schema:"registry"` + Remote string `schema:"remote"` Rm bool `schema:"rm"` - ForceRm bool `schema:"forcerm"` - Memory int64 `schema:"memory"` - MemSwap int64 `schema:"memswap"` - CpuShares uint64 `schema:"cpushares"` // nolint - CpuSetCpus string `schema:"cpusetcpus"` // nolint - CpuPeriod uint64 `schema:"cpuperiod"` // nolint - CpuQuota int64 `schema:"cpuquota"` // nolint - BuildArgs string `schema:"buildargs"` ShmSize int `schema:"shmsize"` Squash bool `schema:"squash"` - Labels string `schema:"labels"` - NetworkMode string `schema:"networkmode"` - Platform string `schema:"platform"` + Tag []string `schema:"t"` Target string `schema:"target"` - Outputs string `schema:"outputs"` - Registry string `schema:"registry"` }{ Dockerfile: "Dockerfile", - Tag: []string{}, + Registry: "docker.io", Rm: true, ShmSize: 64 * 1024 * 1024, - Registry: "docker.io", + Tag: []string{}, } decoder := r.Context().Value("decoder").(*schema.Decoder) @@ -184,6 +185,7 @@ func BuildImage(w http.ResponseWriter, r *http.Request) { CPUQuota: query.CpuQuota, CPUShares: query.CpuShares, CPUSetCPUs: query.CpuSetCpus, + HTTPProxy: query.HTTPProxy, Memory: query.Memory, MemorySwap: query.MemSwap, ShmSize: strconv.Itoa(query.ShmSize), diff --git a/pkg/api/server/idle/tracker.go b/pkg/api/server/idle/tracker.go new file mode 100644 index 000000000..1b378c492 --- /dev/null +++ b/pkg/api/server/idle/tracker.go @@ -0,0 +1,96 @@ +package idle + +import ( + "net" + "net/http" + "sync" + "time" + + "github.com/sirupsen/logrus" +) + +// Tracker holds the state for the server's idle tracking +type Tracker struct { + // Duration is the API idle window + Duration time.Duration + hijacked int // count of active connections managed by handlers + managed map[net.Conn]struct{} // set of active connections managed by http package + mux sync.Mutex // protect managed map + timer *time.Timer + total int // total number of connections made to this server instance +} + +// NewTracker creates and initializes a new Tracker object +// For best behavior, duration should be 2x http idle connection timeout +func NewTracker(idle time.Duration) *Tracker { + return &Tracker{ + managed: make(map[net.Conn]struct{}), + Duration: idle, + timer: time.NewTimer(idle), + } +} + +// ConnState is called on HTTP connection state changes. +// - Once StateHijacked, StateClose is _NOT_ called on that connection +// - There are two "idle" timeouts, the http idle connection (not to be confused with the TCP/IP idle socket timeout) +// and the API idle window. The caller should set the http idle timeout to 2x the time provided to NewTacker() which +// is the API idle window. +func (t *Tracker) ConnState(conn net.Conn, state http.ConnState) { + t.mux.Lock() + defer t.mux.Unlock() + + logrus.Debugf("IdleTracker %p:%v %dm+%dh/%dt connection(s)", conn, state, len(t.managed), t.hijacked, t.TotalConnections()) + switch state { + case http.StateNew, http.StateActive: + // stop the API timer when the server transitions any connection to an "active" state + t.managed[conn] = struct{}{} + t.timer.Stop() + t.total++ + case http.StateHijacked: + // hijacked connections should call Close() when finished. + // Note: If a handler hijack's a connection and then doesn't Close() it, + // the API timer will not fire and the server will _NOT_ timeout. + delete(t.managed, conn) + t.hijacked++ + case http.StateIdle: + // When any connection goes into the http idle state, we know: + // - we have an active connection + // - the API timer should not be counting down (See case StateNew/StateActive) + break + case http.StateClosed: + oldActive := t.ActiveConnections() + + // Either the server or a hijacking handler has closed the http connection to a client + if _, found := t.managed[conn]; found { + delete(t.managed, conn) + } else { + t.hijacked-- // guarded by t.mux above + } + + // Transitioned from any "active" connection to no connections + if oldActive > 0 && t.ActiveConnections() == 0 { + t.timer.Stop() // See library source for Reset() issues and why they are not fixed + t.timer.Reset(t.Duration) // Restart the API window timer + } + } +} + +// Close is used to update Tracker that a StateHijacked connection has been closed by handler (StateClosed) +func (t *Tracker) Close() { + t.ConnState(nil, http.StateClosed) +} + +// ActiveConnections returns the number of current managed or StateHijacked connections +func (t *Tracker) ActiveConnections() int { + return len(t.managed) + t.hijacked +} + +// TotalConnections returns total number of connections made to this instance of the service +func (t *Tracker) TotalConnections() int { + return t.total +} + +// Done is called when idle timer has expired +func (t *Tracker) Done() <-chan time.Time { + return t.timer.C +} diff --git a/pkg/api/server/idletracker/idletracker.go b/pkg/api/server/idletracker/idletracker.go deleted file mode 100644 index 1ee905a99..000000000 --- a/pkg/api/server/idletracker/idletracker.go +++ /dev/null @@ -1,74 +0,0 @@ -package idletracker - -import ( - "net" - "net/http" - "sync" - "time" - - "github.com/sirupsen/logrus" -) - -type IdleTracker struct { - http map[net.Conn]struct{} - hijacked int - total int - mux sync.Mutex - timer *time.Timer - Duration time.Duration -} - -func NewIdleTracker(idle time.Duration) *IdleTracker { - return &IdleTracker{ - http: make(map[net.Conn]struct{}), - Duration: idle, - timer: time.NewTimer(idle), - } -} - -func (t *IdleTracker) ConnState(conn net.Conn, state http.ConnState) { - t.mux.Lock() - defer t.mux.Unlock() - - oldActive := t.ActiveConnections() - logrus.Debugf("IdleTracker %p:%v %d/%d connection(s)", conn, state, oldActive, t.TotalConnections()) - switch state { - case http.StateNew, http.StateActive: - t.http[conn] = struct{}{} - // stop the timer if we transitioned from idle - if oldActive == 0 { - t.timer.Stop() - } - t.total++ - case http.StateHijacked: - // hijacked connections are handled elsewhere - delete(t.http, conn) - t.hijacked++ - case http.StateIdle, http.StateClosed: - delete(t.http, conn) - // Restart the timer if we've become idle - if oldActive > 0 && len(t.http) == 0 { - t.timer.Stop() - t.timer.Reset(t.Duration) - } - } -} - -func (t *IdleTracker) TrackHijackedClosed() { - t.mux.Lock() - defer t.mux.Unlock() - - t.hijacked-- -} - -func (t *IdleTracker) ActiveConnections() int { - return len(t.http) + t.hijacked -} - -func (t *IdleTracker) TotalConnections() int { - return t.total -} - -func (t *IdleTracker) Done() <-chan time.Time { - return t.timer.C -} diff --git a/pkg/api/server/register_images.go b/pkg/api/server/register_images.go index 32e401a54..cb0d26d1e 100644 --- a/pkg/api/server/register_images.go +++ b/pkg/api/server/register_images.go @@ -1423,6 +1423,13 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // description: | // output configuration TBD // (As of version 1.xx) + // - in: query + // name: httpproxy + // type: boolean + // default: + // description: | + // Inject http proxy environment variables into container + // (As of version 2.0.0) // produces: // - application/json // responses: diff --git a/pkg/api/server/server.go b/pkg/api/server/server.go index e7c031234..09a9f6370 100644 --- a/pkg/api/server/server.go +++ b/pkg/api/server/server.go @@ -16,7 +16,7 @@ import ( "github.com/containers/podman/v2/libpod" "github.com/containers/podman/v2/pkg/api/handlers" - "github.com/containers/podman/v2/pkg/api/server/idletracker" + "github.com/containers/podman/v2/pkg/api/server/idle" "github.com/coreos/go-systemd/v22/activation" "github.com/coreos/go-systemd/v22/daemon" "github.com/gorilla/mux" @@ -26,14 +26,14 @@ import ( ) type APIServer struct { - http.Server // The HTTP work happens here - *schema.Decoder // Decoder for Query parameters to structs - context.Context // Context to carry objects to handlers - *libpod.Runtime // Where the real work happens - net.Listener // mux for routing HTTP API calls to libpod routines - context.CancelFunc // Stop APIServer - idleTracker *idletracker.IdleTracker // Track connections to support idle shutdown - pprof *http.Server // Sidecar http server for providing performance data + http.Server // The HTTP work happens here + *schema.Decoder // Decoder for Query parameters to structs + context.Context // Context to carry objects to handlers + *libpod.Runtime // Where the real work happens + net.Listener // mux for routing HTTP API calls to libpod routines + context.CancelFunc // Stop APIServer + idleTracker *idle.Tracker // Track connections to support idle shutdown + pprof *http.Server // Sidecar http server for providing performance data } // Number of seconds to wait for next request, if exceeded shutdown server @@ -70,13 +70,13 @@ func newServer(runtime *libpod.Runtime, duration time.Duration, listener *net.Li } router := mux.NewRouter().UseEncodedPath() - idle := idletracker.NewIdleTracker(duration) + idle := idle.NewTracker(duration) server := APIServer{ Server: http.Server{ Handler: router, ReadHeaderTimeout: 20 * time.Second, - IdleTimeout: duration, + IdleTimeout: duration * 2, ConnState: idle.ConnState, ErrorLog: log.New(logrus.StandardLogger().Out, "", 0), }, diff --git a/pkg/bindings/images/build.go b/pkg/bindings/images/build.go index 9082670a7..e65a04f7b 100644 --- a/pkg/bindings/images/build.go +++ b/pkg/bindings/images/build.go @@ -60,7 +60,7 @@ func Build(ctx context.Context, containerFiles []string, options entities.BuildO params.Set("cpushares", strconv.Itoa(int(cpuShares))) } if cpuSetCpus := options.CommonBuildOpts.CPUSetCPUs; len(cpuSetCpus) > 0 { - params.Set("cpusetcpues", cpuSetCpus) + params.Set("cpusetcpus", cpuSetCpus) } if cpuPeriod := options.CommonBuildOpts.CPUPeriod; cpuPeriod > 0 { params.Set("cpuperiod", strconv.Itoa(int(cpuPeriod))) @@ -92,6 +92,9 @@ func Build(ctx context.Context, containerFiles []string, options entities.BuildO } params.Set("labels", l) } + if options.CommonBuildOpts.HTTPProxy { + params.Set("httpproxy", "1") + } stdout := io.Writer(os.Stdout) if options.Out != nil { diff --git a/pkg/registries/registries.go b/pkg/registries/registries.go index 5dff25c7d..949c5d835 100644 --- a/pkg/registries/registries.go +++ b/pkg/registries/registries.go @@ -1,5 +1,10 @@ package registries +// TODO: this package should not exist anymore. Users should either use +// c/image's `sysregistriesv2` package directly OR, even better, we cache a +// config in libpod's image runtime so we don't need to parse the +// registries.conf files redundantly. + import ( "os" "path/filepath" diff --git a/pkg/specgen/generate/container.go b/pkg/specgen/generate/container.go index 927e6b7fa..2ee8f2441 100644 --- a/pkg/specgen/generate/container.go +++ b/pkg/specgen/generate/container.go @@ -13,6 +13,7 @@ import ( "github.com/containers/podman/v2/pkg/specgen" spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" + "github.com/sirupsen/logrus" "golang.org/x/sys/unix" ) @@ -33,7 +34,43 @@ func CompleteSpec(ctx context.Context, r *libpod.Runtime, s *specgen.SpecGenerat _, mediaType, err := newImage.Manifest(ctx) if err != nil { - return nil, err + if errors.Cause(err) != image.ErrImageIsBareList { + return nil, err + } + // if err is not runnable image + // use the local store image with repo@digest matches with the list, if exists + manifestByte, manifestType, err := newImage.GetManifest(ctx, nil) + if err != nil { + return nil, err + } + list, err := manifest.ListFromBlob(manifestByte, manifestType) + if err != nil { + return nil, err + } + images, err := r.ImageRuntime().GetImages() + if err != nil { + return nil, err + } + findLocal := false + listDigest, err := list.ChooseInstance(r.SystemContext()) + if err != nil { + return nil, err + } + for _, img := range images { + for _, imageDigest := range img.Digests() { + if imageDigest == listDigest { + newImage = img + s.Image = img.ID() + mediaType = manifestType + findLocal = true + logrus.Debug("image contains manifest list, using image from local storage") + break + } + } + } + if !findLocal { + return nil, image.ErrImageIsBareList + } } if s.HealthConfig == nil && mediaType == manifest.DockerV2Schema2MediaType { diff --git a/pkg/specgen/generate/security.go b/pkg/specgen/generate/security.go index 7c818cf62..d17cd4a9a 100644 --- a/pkg/specgen/generate/security.go +++ b/pkg/specgen/generate/security.go @@ -131,12 +131,13 @@ func securityConfigureGenerator(s *specgen.SpecGenerator, g *generate.Generator, } configSpec := g.Config + configSpec.Process.Capabilities.Ambient = []string{} configSpec.Process.Capabilities.Bounding = caplist + configSpec.Process.Capabilities.Inheritable = caplist if s.User == "" || s.User == "root" || s.User == "0" { configSpec.Process.Capabilities.Effective = caplist configSpec.Process.Capabilities.Permitted = caplist - configSpec.Process.Capabilities.Inheritable = caplist } else { userCaps, err := capabilities.NormalizeCapabilities(s.CapAdd) if err != nil { diff --git a/test/e2e/cp_test.go b/test/e2e/cp_test.go index a53485fa4..0a9fa990c 100644 --- a/test/e2e/cp_test.go +++ b/test/e2e/cp_test.go @@ -269,11 +269,11 @@ var _ = Describe("Podman cp", func() { session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) - session = podmanTest.Podman([]string{"exec", "-u", "testuser", "testctr", "touch", "testfile"}) + session = podmanTest.Podman([]string{"exec", "-u", "testuser", "testctr", "touch", "/tmp/testfile"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) - session = podmanTest.Podman([]string{"cp", "--pause=false", "testctr:testfile", "testfile1"}) + session = podmanTest.Podman([]string{"cp", "--pause=false", "testctr:/tmp/testfile", "testfile1"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) diff --git a/test/e2e/create_test.go b/test/e2e/create_test.go index e6491ce9b..96a234446 100644 --- a/test/e2e/create_test.go +++ b/test/e2e/create_test.go @@ -609,4 +609,21 @@ var _ = Describe("Podman create", func() { Expect(session.ExitCode()).ToNot(BeZero()) }) + It("create use local store image if input image contains a manifest list", func() { + session := podmanTest.Podman([]string{"pull", BB}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(BeZero()) + + session = podmanTest.Podman([]string{"manifest", "create", "mylist"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.Podman([]string{"manifest", "add", "--all", "mylist", BB}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(BeZero()) + + session = podmanTest.Podman([]string{"create", "mylist"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(BeZero()) + }) }) diff --git a/test/e2e/network_create_test.go b/test/e2e/network_create_test.go index a81ca19eb..edd76739f 100644 --- a/test/e2e/network_create_test.go +++ b/test/e2e/network_create_test.go @@ -58,7 +58,7 @@ func genericPluginsToPortMap(plugins interface{}, pluginType string) (network.Po func (p *PodmanTestIntegration) removeCNINetwork(name string) { session := p.Podman([]string{"network", "rm", "-f", name}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(BeZero()) + Expect(session.ExitCode()).To(BeNumerically("<=", 1)) } func removeNetworkDevice(name string) { diff --git a/test/e2e/network_test.go b/test/e2e/network_test.go index 79b530815..aae82e292 100644 --- a/test/e2e/network_test.go +++ b/test/e2e/network_test.go @@ -135,7 +135,6 @@ var _ = Describe("Podman network", func() { }) It("podman network rm", func() { - SkipIfRootless("FIXME: This one is definitely broken in rootless mode") // Setup, use uuid to prevent conflict with other tests uuid := stringid.GenerateNonCryptoID() secondPath := filepath.Join(podmanTest.CNIConfigDir, fmt.Sprintf("%s.conflist", uuid)) @@ -276,6 +275,7 @@ var _ = Describe("Podman network", func() { session := podmanTest.Podman([]string{"network", "create", netName}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(BeZero()) + defer podmanTest.removeCNINetwork(netName) session = podmanTest.Podman([]string{"pod", "create", "--network", netName}) session.WaitWithDefaultTimeout() @@ -311,11 +311,13 @@ var _ = Describe("Podman network", func() { session := podmanTest.Podman([]string{"network", "create", netName1}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(BeZero()) + defer podmanTest.removeCNINetwork(netName1) netName2 := "net2" session = podmanTest.Podman([]string{"network", "create", netName2}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(BeZero()) + defer podmanTest.removeCNINetwork(netName2) session = podmanTest.Podman([]string{"network", "rm", netName1, netName2}) session.WaitWithDefaultTimeout() diff --git a/test/e2e/pull_test.go b/test/e2e/pull_test.go index 2280d16cc..edc17fdbf 100644 --- a/test/e2e/pull_test.go +++ b/test/e2e/pull_test.go @@ -1,9 +1,8 @@ package integration import ( - "os" - "fmt" + "os" "path/filepath" "strings" @@ -400,4 +399,101 @@ var _ = Describe("Podman pull", func() { session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Not(Equal(0))) }) + + It("podman pull + inspect from unqualified-search registry", func() { + // Regression test for #6381: + // Make sure that `pull shortname` and `inspect shortname` + // refer to the same image. + + // We already tested pulling, so we can save some energy and + // just restore local artifacts and tag them. + podmanTest.RestoreArtifact(ALPINE) + podmanTest.RestoreArtifact(BB) + + // What we want is at least two images which have the same name + // and are prefixed with two different unqualified-search + // registries from ../registries.conf. + // + // A `podman inspect $name` must yield the one from the _first_ + // matching registry in the registries.conf. + getID := func(image string) string { + setup := podmanTest.PodmanNoCache([]string{"image", "inspect", image}) + setup.WaitWithDefaultTimeout() + Expect(setup.ExitCode()).To(Equal(0)) + + data := setup.InspectImageJSON() // returns []inspect.ImageData + Expect(len(data)).To(Equal(1)) + return data[0].ID + } + + untag := func(image string) { + setup := podmanTest.PodmanNoCache([]string{"untag", image}) + setup.WaitWithDefaultTimeout() + Expect(setup.ExitCode()).To(Equal(0)) + + setup = podmanTest.PodmanNoCache([]string{"image", "inspect", image}) + setup.WaitWithDefaultTimeout() + Expect(setup.ExitCode()).To(Equal(0)) + + data := setup.InspectImageJSON() // returns []inspect.ImageData + Expect(len(data)).To(Equal(1)) + Expect(len(data[0].RepoTags)).To(Equal(0)) + } + + tag := func(image, tag string) { + setup := podmanTest.PodmanNoCache([]string{"tag", image, tag}) + setup.WaitWithDefaultTimeout() + Expect(setup.ExitCode()).To(Equal(0)) + setup = podmanTest.PodmanNoCache([]string{"image", "exists", tag}) + setup.WaitWithDefaultTimeout() + Expect(setup.ExitCode()).To(Equal(0)) + } + + image1 := getID(ALPINE) + image2 := getID(BB) + + // $ head -n2 ../registries.conf + // [registries.search] + // registries = ['docker.io', 'quay.io', 'registry.fedoraproject.org'] + registries := []string{"docker.io", "quay.io", "registry.fedoraproject.org"} + name := "foo/test:tag" + tests := []struct { + // tag1 has precedence (see list above) over tag2 when + // doing an inspect on "test:tag". + tag1, tag2 string + }{ + { + fmt.Sprintf("%s/%s", registries[0], name), + fmt.Sprintf("%s/%s", registries[1], name), + }, + { + fmt.Sprintf("%s/%s", registries[0], name), + fmt.Sprintf("%s/%s", registries[2], name), + }, + { + fmt.Sprintf("%s/%s", registries[1], name), + fmt.Sprintf("%s/%s", registries[2], name), + }, + } + + for _, t := range tests { + // 1) untag both images + // 2) tag them according to `t` + // 3) make sure that an inspect of `name` returns `image1` with `tag1` + untag(image1) + untag(image2) + tag(image1, t.tag1) + tag(image2, t.tag2) + + setup := podmanTest.PodmanNoCache([]string{"image", "inspect", name}) + setup.WaitWithDefaultTimeout() + Expect(setup.ExitCode()).To(Equal(0)) + + data := setup.InspectImageJSON() // returns []inspect.ImageData + Expect(len(data)).To(Equal(1)) + Expect(len(data[0].RepoTags)).To(Equal(1)) + Expect(data[0].RepoTags[0]).To(Equal(t.tag1)) + Expect(data[0].ID).To(Equal(image1)) + } + }) }) diff --git a/test/registries.conf b/test/registries.conf index bb7072d45..f27a282d6 100644 --- a/test/registries.conf +++ b/test/registries.conf @@ -1,3 +1,4 @@ +# Note that changing the order here may break tests. [registries.search] registries = ['docker.io', 'quay.io', 'registry.fedoraproject.org'] diff --git a/test/system/030-run.bats b/test/system/030-run.bats index eb740f04b..766948ecc 100644 --- a/test/system/030-run.bats +++ b/test/system/030-run.bats @@ -319,7 +319,7 @@ echo $rand | 0 | $rand # #6991 : /etc/passwd is modifiable @test "podman run : --userns=keep-id: passwd file is modifiable" { - run_podman run -d --userns=keep-id $IMAGE sh -c 'while ! test -e /stop; do sleep 0.1; done' + run_podman run -d --userns=keep-id --cap-add=dac_override $IMAGE sh -c 'while ! test -e /tmp/stop; do sleep 0.1; done' cid="$output" # Assign a UID that is (a) not in our image /etc/passwd and (b) not @@ -340,7 +340,7 @@ echo $rand | 0 | $rand is "$output" "newuser3:x:$uid:999:$gecos:/home/newuser3:/bin/sh" \ "newuser3 added to /etc/passwd in container" - run_podman exec $cid touch /stop + run_podman exec $cid touch /tmp/stop run_podman wait $cid } diff --git a/test/system/075-exec.bats b/test/system/075-exec.bats index e9db8c27e..edd7dedc4 100644 --- a/test/system/075-exec.bats +++ b/test/system/075-exec.bats @@ -92,14 +92,14 @@ load helpers # #6829 : add username to /etc/passwd inside container if --userns=keep-id @test "podman exec - with keep-id" { run_podman run -d --userns=keep-id $IMAGE sh -c \ - "echo READY;while [ ! -f /stop ]; do sleep 1; done" + "echo READY;while [ ! -f /tmp/stop ]; do sleep 1; done" cid="$output" wait_for_ready $cid run_podman exec $cid id -un is "$output" "$(id -un)" "container is running as current user" - run_podman exec --user=$(id -un) $cid touch /stop + run_podman exec --user=$(id -un) $cid touch /tmp/stop run_podman wait $cid run_podman rm $cid } |