diff options
author | Valentin Rothberg <rothberg@redhat.com> | 2021-04-22 08:01:12 +0200 |
---|---|---|
committer | Valentin Rothberg <rothberg@redhat.com> | 2021-05-05 11:30:12 +0200 |
commit | 0f7d54b0260c1be992ee3b9cee359ef3a9e8bd21 (patch) | |
tree | 192e52054de2abf0c92d83ecdbc71d498c2ec947 /pkg | |
parent | 8eefca5a257121b177562742c972e39e1686140d (diff) | |
download | podman-0f7d54b0260c1be992ee3b9cee359ef3a9e8bd21.tar.gz podman-0f7d54b0260c1be992ee3b9cee359ef3a9e8bd21.tar.bz2 podman-0f7d54b0260c1be992ee3b9cee359ef3a9e8bd21.zip |
migrate Podman to containers/common/libimage
Migrate the Podman code base over to `common/libimage` which replaces
`libpod/image` and a lot of glue code entirely.
Note that I tried to leave bread crumbs for changed tests.
Miscellaneous changes:
* Some errors yield different messages which required to alter some
tests.
* I fixed some pre-existing issues in the code. Others were marked as
`//TODO`s to prevent the PR from exploding.
* The `NamesHistory` of an image is returned as is from the storage.
Previously, we did some filtering which I think is undesirable.
Instead we should return the data as stored in the storage.
* Touched handlers use the ABI interfaces where possible.
* Local image resolution: previously Podman would match "foo" on
"myfoo". This behaviour has been changed and Podman will now
only match on repository boundaries such that "foo" would match
"my/foo" but not "myfoo". I consider the old behaviour to be a
bug, at the very least an exotic corner case.
* Futhermore, "foo:none" does *not* resolve to a local image "foo"
without tag anymore. It's a hill I am (almost) willing to die on.
* `image prune` prints the IDs of pruned images. Previously, in some
cases, the names were printed instead. The API clearly states ID,
so we should stick to it.
* Compat endpoint image removal with _force_ deletes the entire not
only the specified tag.
Signed-off-by: Valentin Rothberg <rothberg@redhat.com>
Diffstat (limited to 'pkg')
43 files changed, 995 insertions, 1215 deletions
diff --git a/pkg/api/handlers/compat/containers_create.go b/pkg/api/handlers/compat/containers_create.go index 93934f1de..162a98135 100644 --- a/pkg/api/handlers/compat/containers_create.go +++ b/pkg/api/handlers/compat/containers_create.go @@ -6,12 +6,12 @@ import ( "github.com/containers/podman/v3/cmd/podman/common" "github.com/containers/podman/v3/libpod" - "github.com/containers/podman/v3/libpod/define" "github.com/containers/podman/v3/pkg/api/handlers" "github.com/containers/podman/v3/pkg/api/handlers/utils" "github.com/containers/podman/v3/pkg/domain/entities" "github.com/containers/podman/v3/pkg/domain/infra/abi" "github.com/containers/podman/v3/pkg/specgen" + "github.com/containers/storage" "github.com/gorilla/schema" "github.com/pkg/errors" ) @@ -50,14 +50,14 @@ func CreateContainer(w http.ResponseWriter, r *http.Request) { return } - newImage, err := runtime.ImageRuntime().NewFromLocal(body.Config.Image) + newImage, resolvedName, err := runtime.LibimageRuntime().LookupImage(body.Config.Image, nil) if err != nil { - if errors.Cause(err) == define.ErrNoSuchImage { + if errors.Cause(err) == storage.ErrImageUnknown { utils.Error(w, "No such image", http.StatusNotFound, err) return } - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "NewFromLocal()")) + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "error looking up image")) return } @@ -71,7 +71,7 @@ func CreateContainer(w http.ResponseWriter, r *http.Request) { 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) + imageRef, err := utils.ParseDockerReference(resolvedName) if err != nil { utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, err) return diff --git a/pkg/api/handlers/compat/images.go b/pkg/api/handlers/compat/images.go index e5caa9ea5..0b9367a17 100644 --- a/pkg/api/handlers/compat/images.go +++ b/pkg/api/handlers/compat/images.go @@ -4,23 +4,24 @@ import ( "context" "encoding/json" "fmt" - "io" "io/ioutil" "net/http" "os" "strings" "github.com/containers/buildah" + "github.com/containers/common/libimage" + "github.com/containers/common/pkg/config" "github.com/containers/image/v5/manifest" "github.com/containers/image/v5/types" "github.com/containers/podman/v3/libpod" - image2 "github.com/containers/podman/v3/libpod/image" "github.com/containers/podman/v3/pkg/api/handlers" "github.com/containers/podman/v3/pkg/api/handlers/utils" "github.com/containers/podman/v3/pkg/auth" "github.com/containers/podman/v3/pkg/channel" "github.com/containers/podman/v3/pkg/domain/entities" - "github.com/containers/podman/v3/pkg/util" + "github.com/containers/podman/v3/pkg/domain/infra/abi" + "github.com/containers/storage" "github.com/gorilla/schema" "github.com/opencontainers/go-digest" "github.com/pkg/errors" @@ -47,26 +48,35 @@ func ExportImage(w http.ResponseWriter, r *http.Request) { // 500 server runtime := r.Context().Value("runtime").(*libpod.Runtime) - name := utils.GetName(r) - newImage, err := runtime.ImageRuntime().NewFromLocal(name) - if err != nil { - utils.ImageNotFound(w, name, errors.Wrapf(err, "failed to find image %s", name)) - return - } tmpfile, err := ioutil.TempFile("", "api.tar") if err != nil { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to create tempfile")) return } defer os.Remove(tmpfile.Name()) - if err := tmpfile.Close(); err != nil { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to close tempfile")) + + name := utils.GetName(r) + imageEngine := abi.ImageEngine{Libpod: runtime} + + saveOptions := entities.ImageSaveOptions{ + Format: "docker-archive", + Output: tmpfile.Name(), + } + + if err := imageEngine.Save(r.Context(), name, nil, saveOptions); err != nil { + if errors.Cause(err) == storage.ErrImageUnknown { + utils.ImageNotFound(w, name, errors.Wrapf(err, "failed to find image %s", name)) + return + } + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to create tempfile")) return } - if err := newImage.Save(r.Context(), name, "docker-archive", tmpfile.Name(), []string{}, false, false, true); err != nil { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to save image")) + + if err := tmpfile.Close(); err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to close tempfile")) return } + rdr, err := os.Open(tmpfile.Name()) if err != nil { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to read the exported tarfile")) @@ -105,7 +115,7 @@ func CommitContainer(w http.ResponseWriter, r *http.Request) { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()")) return } - sc := image2.GetSystemContext(rtc.Engine.SignaturePolicyPath, "", false) + sc := runtime.SystemContext() tag := "latest" options := libpod.ContainerCommitOptions{ Pause: true, @@ -180,20 +190,13 @@ func CreateImageFromSrc(w http.ResponseWriter, r *http.Request) { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to write temporary file")) } } - iid, err := runtime.Import(r.Context(), source, "", "", query.Changes, "", false) + + imageEngine := abi.ImageEngine{Libpod: runtime} + report, err := imageEngine.Import(r.Context(), entities.ImageImportOptions{Source: source, Changes: query.Changes}) if err != nil { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to import tarball")) return } - tmpfile, err := ioutil.TempFile("", "fromsrc.tar") - if err != nil { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to create tempfile")) - return - } - if err := tmpfile.Close(); err != nil { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to close tempfile")) - return - } // Success utils.WriteResponse(w, http.StatusOK, struct { Status string `json:"status"` @@ -201,9 +204,9 @@ func CreateImageFromSrc(w http.ResponseWriter, r *http.Request) { ProgressDetail map[string]string `json:"progressDetail"` Id string `json:"id"` // nolint }{ - Status: iid, + Status: report.Id, ProgressDetail: map[string]string{}, - Id: iid, + Id: report.Id, }) } @@ -235,36 +238,34 @@ func CreateImageFromImage(w http.ResponseWriter, r *http.Request) { } defer auth.RemoveAuthfile(authfile) - registryOpts := image2.DockerRegistryOptions{DockerRegistryCreds: authConf} - if sys := runtime.SystemContext(); sys != nil { - registryOpts.DockerCertPath = sys.DockerCertPath + pullOptions := &libimage.PullOptions{} + pullOptions.AuthFilePath = authfile + if authConf != nil { + pullOptions.Username = authConf.Username + pullOptions.Password = authConf.Password + pullOptions.IdentityToken = authConf.IdentityToken } + pullOptions.Writer = os.Stderr // allows for debugging on the server stderr := channel.NewWriter(make(chan []byte)) defer stderr.Close() progress := make(chan types.ProgressProperties) + pullOptions.Progress = progress var img string runCtx, cancel := context.WithCancel(context.Background()) go func() { defer cancel() - - newImage, err := runtime.ImageRuntime().New( - runCtx, - fromImage, - "", // signature policy - authfile, - nil, // writer - ®istryOpts, - image2.SigningOptions{}, - nil, // label - util.PullImageAlways, - progress) + pulledImages, err := runtime.LibimageRuntime().Pull(runCtx, fromImage, config.PullPolicyAlways, pullOptions) if err != nil { stderr.Write([]byte(err.Error() + "\n")) } else { - img = newImage.ID() + if len(pulledImages) == 0 { + utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.New("internal error: no images pulled")) + return + } + img = pulledImages[0].ID() } }() @@ -347,7 +348,7 @@ func GetImage(w http.ResponseWriter, r *http.Request) { if err != nil { // Here we need to fiddle with the error message because docker-py is looking for "No // such image" to determine on how to raise the correct exception. - errMsg := strings.ReplaceAll(err.Error(), "no such image", "No such image") + errMsg := strings.ReplaceAll(err.Error(), "image not known", "No such image") utils.Error(w, "Something went wrong.", http.StatusNotFound, errors.Errorf("failed to find image %s: %s", name, errMsg)) return } @@ -379,13 +380,14 @@ func GetImages(w http.ResponseWriter, r *http.Request) { func LoadImages(w http.ResponseWriter, r *http.Request) { // TODO this is basically wrong + // TODO ... improve these ^ messages to something useful decoder := r.Context().Value("decoder").(*schema.Decoder) runtime := r.Context().Value("runtime").(*libpod.Runtime) query := struct { - Changes map[string]string `json:"changes"` - Message string `json:"message"` - Quiet bool `json:"quiet"` + Changes map[string]string `json:"changes"` // Ignored + Message string `json:"message"` // Ignored + Quiet bool `json:"quiet"` // Ignored }{ // This is where you can override the golang default value for one of fields } @@ -395,10 +397,8 @@ func LoadImages(w http.ResponseWriter, r *http.Request) { return } - var ( - err error - writer io.Writer - ) + // First write the body to a temporary file that we can later attempt + // to load. f, err := ioutil.TempFile("", "api_load.tar") if err != nil { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to create tempfile")) @@ -414,15 +414,25 @@ func LoadImages(w http.ResponseWriter, r *http.Request) { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to write temporary file")) return } - id, err := runtime.LoadImage(r.Context(), f.Name(), writer, "") + + imageEngine := abi.ImageEngine{Libpod: runtime} + + loadOptions := entities.ImageLoadOptions{Input: f.Name()} + loadReport, err := imageEngine.Load(r.Context(), loadOptions) if err != nil { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to load image")) return } + + if len(loadReport.Names) != 1 { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Errorf("%d instead of 1 were loaded", len(loadReport.Names))) + return + } + utils.WriteResponse(w, http.StatusOK, struct { Stream string `json:"stream"` }{ - Stream: fmt.Sprintf("Loaded image: %s\n", id), + Stream: fmt.Sprintf("Loaded image: %s\n", loadReport.Names[0]), }) } @@ -453,10 +463,15 @@ func ExportImages(w http.ResponseWriter, r *http.Request) { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to close tempfile")) return } - if err := runtime.ImageRuntime().SaveImages(r.Context(), images, "docker-archive", tmpfile.Name(), false, true); err != nil { + + imageEngine := abi.ImageEngine{Libpod: runtime} + + saveOptions := entities.ImageSaveOptions{Output: tmpfile.Name()} + if err := imageEngine.Save(r.Context(), images[0], images[1:], saveOptions); err != nil { utils.InternalServerError(w, err) return } + rdr, err := os.Open(tmpfile.Name()) if err != nil { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to read the exported tarfile")) diff --git a/pkg/api/handlers/compat/images_history.go b/pkg/api/handlers/compat/images_history.go index a02ed179c..ea596890f 100644 --- a/pkg/api/handlers/compat/images_history.go +++ b/pkg/api/handlers/compat/images_history.go @@ -3,6 +3,7 @@ package compat import ( "net/http" + "github.com/containers/common/libimage" "github.com/containers/podman/v3/libpod" "github.com/containers/podman/v3/pkg/api/handlers" "github.com/containers/podman/v3/pkg/api/handlers/utils" @@ -13,7 +14,8 @@ func HistoryImage(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value("runtime").(*libpod.Runtime) name := utils.GetName(r) - newImage, err := runtime.ImageRuntime().NewFromLocal(name) + lookupOptions := &libimage.LookupImageOptions{IgnorePlatform: true} + newImage, _, err := runtime.LibimageRuntime().LookupImage(name, lookupOptions) if err != nil { utils.Error(w, "Something went wrong.", http.StatusNotFound, errors.Wrapf(err, "failed to find image %s", name)) return diff --git a/pkg/api/handlers/compat/images_prune.go b/pkg/api/handlers/compat/images_prune.go index ddf559ec6..bbbfb5577 100644 --- a/pkg/api/handlers/compat/images_prune.go +++ b/pkg/api/handlers/compat/images_prune.go @@ -8,6 +8,8 @@ import ( "github.com/containers/podman/v3/libpod" "github.com/containers/podman/v3/pkg/api/handlers" "github.com/containers/podman/v3/pkg/api/handlers/utils" + "github.com/containers/podman/v3/pkg/domain/entities" + "github.com/containers/podman/v3/pkg/domain/infra/abi" "github.com/containers/podman/v3/pkg/util" "github.com/docker/docker/api/types" "github.com/pkg/errors" @@ -30,7 +32,11 @@ func PruneImages(w http.ResponseWriter, r *http.Request) { filters = append(filters, fmt.Sprintf("%s=%s", k, val)) } } - imagePruneReports, err := runtime.ImageRuntime().PruneImages(r.Context(), false, filters) + + imageEngine := abi.ImageEngine{Libpod: runtime} + + pruneOptions := entities.ImagePruneOptions{Filter: filters} + imagePruneReports, err := imageEngine.Prune(r.Context(), pruneOptions) if err != nil { utils.InternalServerError(w, err) return diff --git a/pkg/api/handlers/compat/images_remove.go b/pkg/api/handlers/compat/images_remove.go index e89558a86..390f25caf 100644 --- a/pkg/api/handlers/compat/images_remove.go +++ b/pkg/api/handlers/compat/images_remove.go @@ -4,10 +4,10 @@ import ( "net/http" "github.com/containers/podman/v3/libpod" - "github.com/containers/podman/v3/libpod/define" "github.com/containers/podman/v3/pkg/api/handlers/utils" "github.com/containers/podman/v3/pkg/domain/entities" "github.com/containers/podman/v3/pkg/domain/infra/abi" + "github.com/containers/storage" "github.com/gorilla/schema" "github.com/pkg/errors" ) @@ -41,7 +41,7 @@ func RemoveImage(w http.ResponseWriter, r *http.Request) { report, rmerrors := imageEngine.Remove(r.Context(), []string{name}, options) if len(rmerrors) > 0 && rmerrors[0] != nil { err := rmerrors[0] - if errors.Cause(err) == define.ErrNoSuchImage { + if errors.Cause(err) == storage.ErrImageUnknown { utils.ImageNotFound(w, name, errors.Wrapf(err, "failed to find image %s", name)) return } diff --git a/pkg/api/handlers/compat/images_search.go b/pkg/api/handlers/compat/images_search.go index 18974f424..13a3693fa 100644 --- a/pkg/api/handlers/compat/images_search.go +++ b/pkg/api/handlers/compat/images_search.go @@ -6,11 +6,11 @@ import ( "github.com/containers/image/v5/types" "github.com/containers/podman/v3/libpod" - "github.com/containers/podman/v3/libpod/define" "github.com/containers/podman/v3/pkg/api/handlers/utils" "github.com/containers/podman/v3/pkg/auth" "github.com/containers/podman/v3/pkg/domain/entities" "github.com/containers/podman/v3/pkg/domain/infra/abi" + "github.com/containers/storage" "github.com/gorilla/schema" "github.com/pkg/errors" ) @@ -64,7 +64,7 @@ func SearchImages(w http.ResponseWriter, r *http.Request) { } if !utils.IsLibpodRequest(r) { if len(reports) == 0 { - utils.ImageNotFound(w, query.Term, define.ErrNoSuchImage) + utils.ImageNotFound(w, query.Term, storage.ErrImageUnknown) return } } diff --git a/pkg/api/handlers/compat/images_tag.go b/pkg/api/handlers/compat/images_tag.go index 0d0c204f3..8d256f4fa 100644 --- a/pkg/api/handlers/compat/images_tag.go +++ b/pkg/api/handlers/compat/images_tag.go @@ -4,6 +4,7 @@ import ( "fmt" "net/http" + "github.com/containers/common/libimage" "github.com/containers/podman/v3/libpod" "github.com/containers/podman/v3/pkg/api/handlers/utils" "github.com/pkg/errors" @@ -14,11 +15,14 @@ func TagImage(w http.ResponseWriter, r *http.Request) { // /v1.xx/images/(name)/tag name := utils.GetName(r) - newImage, err := runtime.ImageRuntime().NewFromLocal(name) + + lookupOptions := &libimage.LookupImageOptions{IgnorePlatform: true} + newImage, _, err := runtime.LibimageRuntime().LookupImage(name, lookupOptions) if err != nil { utils.ImageNotFound(w, name, errors.Wrapf(err, "failed to find image %s", name)) return } + tag := "latest" if len(r.Form.Get("tag")) > 0 { tag = r.Form.Get("tag") @@ -29,7 +33,7 @@ func TagImage(w http.ResponseWriter, r *http.Request) { } repo := r.Form.Get("repo") tagName := fmt.Sprintf("%s:%s", repo, tag) - if err := newImage.TagImage(tagName); err != nil { + if err := newImage.Tag(tagName); err != nil { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err) return } diff --git a/pkg/api/handlers/libpod/images.go b/pkg/api/handlers/libpod/images.go index 92882cc40..a90408bfd 100644 --- a/pkg/api/handlers/libpod/images.go +++ b/pkg/api/handlers/libpod/images.go @@ -11,11 +11,12 @@ import ( "strings" "github.com/containers/buildah" + "github.com/containers/common/libimage" + "github.com/containers/common/pkg/filters" "github.com/containers/image/v5/manifest" "github.com/containers/image/v5/types" "github.com/containers/podman/v3/libpod" "github.com/containers/podman/v3/libpod/define" - "github.com/containers/podman/v3/libpod/image" "github.com/containers/podman/v3/pkg/api/handlers" "github.com/containers/podman/v3/pkg/api/handlers/utils" "github.com/containers/podman/v3/pkg/auth" @@ -24,6 +25,7 @@ import ( "github.com/containers/podman/v3/pkg/errorhandling" "github.com/containers/podman/v3/pkg/util" utils2 "github.com/containers/podman/v3/utils" + "github.com/containers/storage" "github.com/gorilla/schema" "github.com/pkg/errors" ) @@ -74,7 +76,7 @@ func ImageTree(w http.ResponseWriter, r *http.Request) { options := entities.ImageTreeOptions{WhatRequires: query.WhatRequires} report, err := ir.Tree(r.Context(), name, options) if err != nil { - if errors.Cause(err) == define.ErrNoSuchImage { + if errors.Cause(err) == storage.ErrImageUnknown { utils.Error(w, "Something went wrong.", http.StatusNotFound, errors.Wrapf(err, "failed to find image %s", name)) return } @@ -91,7 +93,7 @@ func GetImage(w http.ResponseWriter, r *http.Request) { utils.Error(w, "Something went wrong.", http.StatusNotFound, errors.Wrapf(err, "failed to find image %s", name)) return } - inspect, err := newImage.Inspect(r.Context()) + inspect, err := newImage.Inspect(r.Context(), true) if err != nil { utils.Error(w, "Server error", http.StatusInternalServerError, errors.Wrapf(err, "failed in inspect image %s", inspect.ID)) return @@ -100,22 +102,44 @@ func GetImage(w http.ResponseWriter, r *http.Request) { } func GetImages(w http.ResponseWriter, r *http.Request) { - images, err := utils.GetImages(w, r) + decoder := r.Context().Value("decoder").(*schema.Decoder) + runtime := r.Context().Value("runtime").(*libpod.Runtime) + query := struct { + All bool + Digests bool + Filter string // Docker 1.24 compatibility + }{ + // 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, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, + errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) + return + } + if _, found := r.URL.Query()["digests"]; found && query.Digests { + utils.UnSupportedParameter("digests") + return + } + + filterList, err := filters.FiltersFromRequest(r) if err != nil { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Failed get images")) + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err) return } - var summaries = make([]*entities.ImageSummary, len(images)) - for j, img := range images { - is, err := handlers.ImageToImageSummary(img) - if err != nil { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Failed transform image summaries")) - return - } - // libpod has additional fields that we need to populate. - is.ReadOnly = img.IsReadOnly() - summaries[j] = is + if !utils.IsLibpodRequest(r) && len(query.Filter) > 0 { // Docker 1.24 compatibility + filterList = append(filterList, "reference="+query.Filter) } + + imageEngine := abi.ImageEngine{Libpod: runtime} + + listOptions := entities.ImageListOptions{All: query.All, Filter: filterList} + summaries, err := imageEngine.List(r.Context(), listOptions) + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err) + return + } + utils.WriteResponse(w, http.StatusOK, summaries) } @@ -135,7 +159,8 @@ func PruneImages(w http.ResponseWriter, r *http.Request) { if dErr := decoder.Decode(&query, r.URL.Query()); dErr != nil || err != nil { utils.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError, - errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) + errors. + Wrapf(err, "failed to parse parameters for %s", r.URL.String())) return } @@ -156,7 +181,13 @@ func PruneImages(w http.ResponseWriter, r *http.Request) { } } - imagePruneReports, err := runtime.ImageRuntime().PruneImages(r.Context(), query.All, libpodFilters) + imageEngine := abi.ImageEngine{Libpod: runtime} + + pruneOptions := entities.ImagePruneOptions{ + All: query.All, + Filter: libpodFilters, + } + imagePruneReports, err := imageEngine.Prune(r.Context(), pruneOptions) if err != nil { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err) return @@ -183,11 +214,13 @@ func ExportImage(w http.ResponseWriter, r *http.Request) { return } name := utils.GetName(r) - newImage, err := runtime.ImageRuntime().NewFromLocal(name) - if err != nil { + + lookupOptions := &libimage.LookupImageOptions{IgnorePlatform: true} + if _, _, err := runtime.LibimageRuntime().LookupImage(name, lookupOptions); err != nil { utils.ImageNotFound(w, name, err) return } + switch query.Format { case define.OCIArchive, define.V2s2Archive: tmpfile, err := ioutil.TempFile("", "api.tar") @@ -211,7 +244,15 @@ func ExportImage(w http.ResponseWriter, r *http.Request) { utils.Error(w, "unknown format", http.StatusInternalServerError, errors.Errorf("unknown format %q", query.Format)) return } - if err := newImage.Save(r.Context(), name, query.Format, output, []string{}, false, query.Compress, true); err != nil { + + imageEngine := abi.ImageEngine{Libpod: runtime} + + saveOptions := entities.ImageSaveOptions{ + Compress: query.Compress, + Format: query.Format, + Output: output, + } + if err := imageEngine.Save(r.Context(), name, nil, saveOptions); err != nil { utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, err) return } @@ -347,12 +388,15 @@ func ImagesLoad(w http.ResponseWriter, r *http.Request) { return } - loadedImage, err := runtime.LoadImage(context.Background(), tmpfile.Name(), os.Stderr, "") + imageEngine := abi.ImageEngine{Libpod: runtime} + + loadOptions := entities.ImageLoadOptions{Input: tmpfile.Name()} + loadReport, err := imageEngine.Load(r.Context(), loadOptions) if err != nil { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to load image")) return } - utils.WriteResponse(w, http.StatusOK, entities.ImageLoadReport{Names: strings.Split(loadedImage, ",")}) + utils.WriteResponse(w, http.StatusOK, loadReport) } func ImagesImport(w http.ResponseWriter, r *http.Request) { @@ -392,13 +436,21 @@ func ImagesImport(w http.ResponseWriter, r *http.Request) { tmpfile.Close() source = tmpfile.Name() } - importedImage, err := runtime.Import(context.Background(), source, query.Reference, "", query.Changes, query.Message, true) + + imageEngine := abi.ImageEngine{Libpod: runtime} + importOptions := entities.ImageImportOptions{ + Changes: query.Changes, + Message: query.Message, + Reference: query.Reference, + Source: source, + } + report, err := imageEngine.Import(r.Context(), importOptions) if err != nil { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to import image")) + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to import tarball")) return } - utils.WriteResponse(w, http.StatusOK, entities.ImageImportReport{Id: importedImage}) + utils.WriteResponse(w, http.StatusOK, report) } // PushImage is the handler for the compat http endpoint for pushing images. @@ -497,7 +549,7 @@ func CommitContainer(w http.ResponseWriter, r *http.Request) { utils.Error(w, "failed to get runtime config", http.StatusInternalServerError, errors.Wrap(err, "failed to get runtime config")) return } - sc := image.GetSystemContext(rtc.Engine.SignaturePolicyPath, "", false) + sc := runtime.SystemContext() tag := "latest" options := libpod.ContainerCommitOptions{ Pause: true, @@ -579,7 +631,7 @@ func UntagImage(w http.ResponseWriter, r *http.Request) { name := utils.GetName(r) if err := imageEngine.Untag(r.Context(), name, tags, opts); err != nil { - if errors.Cause(err) == define.ErrNoSuchImage { + if errors.Cause(err) == storage.ErrImageUnknown { utils.ImageNotFound(w, name, errors.Wrapf(err, "failed to find image %s", name)) } else { utils.Error(w, "failed to untag", http.StatusInternalServerError, err) diff --git a/pkg/api/handlers/libpod/images_pull.go b/pkg/api/handlers/libpod/images_pull.go index e2e4b53b4..7545ba235 100644 --- a/pkg/api/handlers/libpod/images_pull.go +++ b/pkg/api/handlers/libpod/images_pull.go @@ -3,20 +3,16 @@ package libpod import ( "context" "encoding/json" - "fmt" "net/http" - "strings" - "github.com/containers/image/v5/docker" - "github.com/containers/image/v5/docker/reference" + "github.com/containers/common/libimage" + "github.com/containers/common/pkg/config" "github.com/containers/image/v5/types" "github.com/containers/podman/v3/libpod" - "github.com/containers/podman/v3/libpod/image" "github.com/containers/podman/v3/pkg/api/handlers/utils" "github.com/containers/podman/v3/pkg/auth" "github.com/containers/podman/v3/pkg/channel" "github.com/containers/podman/v3/pkg/domain/entities" - "github.com/containers/podman/v3/pkg/util" "github.com/gorilla/schema" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -51,28 +47,23 @@ func ImagesPull(w http.ResponseWriter, r *http.Request) { return } - imageRef, err := utils.ParseDockerReference(query.Reference) - if err != nil { + // Make sure that the reference has no transport or the docker one. + if _, err := utils.ParseDockerReference(query.Reference); err != nil { utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, err) return } - // Trim the docker-transport prefix. - rawImage := strings.TrimPrefix(query.Reference, fmt.Sprintf("%s://", docker.Transport.Name())) + pullOptions := &libimage.PullOptions{} + pullOptions.AllTags = query.AllTags + pullOptions.Architecture = query.Arch + pullOptions.OS = query.OS + pullOptions.Variant = query.Variant - // all-tags doesn't work with a tagged reference, so let's check early - namedRef, err := reference.Parse(rawImage) - if err != nil { - utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, - errors.Wrapf(err, "error parsing reference %q", rawImage)) - return - } - if _, isTagged := namedRef.(reference.Tagged); isTagged && query.AllTags { - utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, - errors.Errorf("reference %q must not have a tag for all-tags", rawImage)) - return + if _, found := r.URL.Query()["tlsVerify"]; found { + pullOptions.InsecureSkipTLSVerify = types.NewOptionalBool(!query.TLSVerify) } + // Do the auth dance. authConf, authfile, key, err := auth.GetCredentials(r) if err != nil { utils.Error(w, "failed to retrieve repository credentials", http.StatusBadRequest, errors.Wrapf(err, "failed to parse %q header for %s", key, r.URL.String())) @@ -80,71 +71,25 @@ func ImagesPull(w http.ResponseWriter, r *http.Request) { } defer auth.RemoveAuthfile(authfile) - // Setup the registry options - dockerRegistryOptions := image.DockerRegistryOptions{ - DockerRegistryCreds: authConf, - OSChoice: query.OS, - ArchitectureChoice: query.Arch, - VariantChoice: query.Variant, - } - if _, found := r.URL.Query()["tlsVerify"]; found { - dockerRegistryOptions.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!query.TLSVerify) - } - - sys := runtime.SystemContext() - if sys == nil { - sys = image.GetSystemContext("", authfile, false) - } - dockerRegistryOptions.DockerCertPath = sys.DockerCertPath - sys.DockerAuthConfig = authConf - - // Prepare the images we want to pull - imagesToPull := []string{} - imageName := namedRef.String() - - if !query.AllTags { - imagesToPull = append(imagesToPull, imageName) - } else { - tags, err := docker.GetRepositoryTags(context.Background(), sys, imageRef) - if err != nil { - utils.InternalServerError(w, errors.Wrap(err, "error getting repository tags")) - return - } - for _, tag := range tags { - imagesToPull = append(imagesToPull, fmt.Sprintf("%s:%s", imageName, tag)) - } + pullOptions.AuthFilePath = authfile + if authConf != nil { + pullOptions.Username = authConf.Username + pullOptions.Password = authConf.Password + pullOptions.IdentityToken = authConf.IdentityToken } writer := channel.NewWriter(make(chan []byte)) defer writer.Close() - stderr := channel.NewWriter(make(chan []byte)) - defer stderr.Close() + pullOptions.Writer = writer - images := make([]string, 0, len(imagesToPull)) + var pulledImages []*libimage.Image + var pullError error runCtx, cancel := context.WithCancel(context.Background()) - go func(imgs []string) { + go func() { defer cancel() - // Finally pull the images - for _, img := range imgs { - newImage, err := runtime.ImageRuntime().New( - runCtx, - img, - "", - authfile, - writer, - &dockerRegistryOptions, - image.SigningOptions{}, - nil, - util.PullImageAlways, - nil) - if err != nil { - stderr.Write([]byte(err.Error() + "\n")) - } else { - images = append(images, newImage.ID()) - } - } - }(imagesToPull) + pulledImages, pullError = runtime.LibimageRuntime().Pull(runCtx, query.Reference, config.PullPolicyAlways, pullOptions) + }() flush := func() { if flusher, ok := w.(http.Flusher); ok { @@ -158,45 +103,32 @@ func ImagesPull(w http.ResponseWriter, r *http.Request) { enc := json.NewEncoder(w) enc.SetEscapeHTML(true) - var failed bool -loop: // break out of for/select infinite loop for { var report entities.ImagePullReport select { - case e := <-writer.Chan(): - report.Stream = string(e) - if err := enc.Encode(report); err != nil { - stderr.Write([]byte(err.Error())) - } - flush() - case e := <-stderr.Chan(): - failed = true - report.Error = string(e) + case s := <-writer.Chan(): + report.Stream = string(s) if err := enc.Encode(report); err != nil { - logrus.Warnf("Failed to json encode error %q", err.Error()) + logrus.Warnf("Failed to encode json: %v", err) } flush() case <-runCtx.Done(): - if !failed { - // Send all image id's pulled in 'images' stanza - report.Images = images - if err := enc.Encode(report); err != nil { - logrus.Warnf("Failed to json encode error %q", err.Error()) - } - - report.Images = nil + for _, image := range pulledImages { + report.Images = append(report.Images, image.ID()) // Pull last ID from list and publish in 'id' stanza. This maintains previous API contract - report.ID = images[len(images)-1] - if err := enc.Encode(report); err != nil { - logrus.Warnf("Failed to json encode error %q", err.Error()) - } - - flush() + report.ID = image.ID() + } + if pullError != nil { + report.Error = pullError.Error() + } + if err := enc.Encode(report); err != nil { + logrus.Warnf("Failed to encode json: %v", err) } - break loop // break out of for/select infinite loop + flush() + return case <-r.Context().Done(): // Client has closed connection - break loop // break out of for/select infinite loop + return } } } diff --git a/pkg/api/handlers/libpod/manifests.go b/pkg/api/handlers/libpod/manifests.go index 6a491ae48..f21eb2e80 100644 --- a/pkg/api/handlers/libpod/manifests.go +++ b/pkg/api/handlers/libpod/manifests.go @@ -9,7 +9,6 @@ import ( "github.com/containers/image/v5/manifest" "github.com/containers/image/v5/types" "github.com/containers/podman/v3/libpod" - "github.com/containers/podman/v3/libpod/image" "github.com/containers/podman/v3/pkg/api/handlers" "github.com/containers/podman/v3/pkg/api/handlers/utils" "github.com/containers/podman/v3/pkg/auth" @@ -45,13 +44,10 @@ func ManifestCreate(w http.ResponseWriter, r *http.Request) { } } - rtc, err := runtime.GetConfig() - if err != nil { - utils.InternalServerError(w, err) - return - } - sc := image.GetSystemContext(rtc.Engine.SignaturePolicyPath, "", false) - manID, err := image.CreateManifestList(runtime.ImageRuntime(), *sc, query.Name, query.Image, query.All) + imageEngine := abi.ImageEngine{Libpod: runtime} + + createOptions := entities.ManifestCreateOptions{All: query.All} + manID, err := imageEngine.ManifestCreate(r.Context(), query.Name, query.Image, createOptions) if err != nil { utils.InternalServerError(w, err) return @@ -64,8 +60,8 @@ func ExistsManifest(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value("runtime").(*libpod.Runtime) name := utils.GetName(r) - ic := abi.ImageEngine{Libpod: runtime} - report, err := ic.ManifestExists(r.Context(), name) + imageEngine := abi.ImageEngine{Libpod: runtime} + report, err := imageEngine.ManifestExists(r.Context(), name) if err != nil { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err) return @@ -80,45 +76,46 @@ func ExistsManifest(w http.ResponseWriter, r *http.Request) { func ManifestInspect(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value("runtime").(*libpod.Runtime) name := utils.GetName(r) + imageEngine := abi.ImageEngine{Libpod: runtime} - inspectReport, inspectError := imageEngine.ManifestInspect(r.Context(), name) - if inspectError != nil { - utils.Error(w, "Something went wrong.", http.StatusNotFound, inspectError) + rawManifest, err := imageEngine.ManifestInspect(r.Context(), name) + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusNotFound, err) return } - var list manifest.Schema2List - if err := json.Unmarshal(inspectReport, &list); err != nil { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Unmarshal()")) + var schema2List manifest.Schema2List + if err := json.Unmarshal(rawManifest, &schema2List); err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err) return } - if list.Manifests == nil { - list.Manifests = make([]manifest.Schema2ManifestDescriptor, 0) - } - utils.WriteResponse(w, http.StatusOK, &list) + utils.WriteResponse(w, http.StatusOK, schema2List) } func ManifestAdd(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value("runtime").(*libpod.Runtime) - var manifestInput image.ManifestAddOpts - if err := json.NewDecoder(r.Body).Decode(&manifestInput); err != nil { + var addOptions entities.ManifestAddOptions + if err := json.NewDecoder(r.Body).Decode(&addOptions); err != nil { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()")) return } + name := utils.GetName(r) - newImage, err := runtime.ImageRuntime().NewFromLocal(name) - if err != nil { - utils.ImageNotFound(w, name, err) + if _, err := runtime.LibimageRuntime().LookupManifestList(name); err != nil { + utils.Error(w, "Something went wrong.", http.StatusNotFound, err) return } - rtc, err := runtime.GetConfig() - if err != nil { - utils.InternalServerError(w, err) - return + + // FIXME: we really need to clean up the manifest API. Swagger states + // the arguments were strings not string slices. The use of string + // slices, mixing lists and images is incredibly confusing. + if len(addOptions.Images) == 1 { + addOptions.Images = append(addOptions.Images, name) } - sc := image.GetSystemContext(rtc.Engine.SignaturePolicyPath, "", false) - newID, err := newImage.AddManifest(*sc, manifestInput) + + imageEngine := abi.ImageEngine{Libpod: runtime} + newID, err := imageEngine.ManifestAdd(r.Context(), addOptions) if err != nil { utils.InternalServerError(w, err) return @@ -140,9 +137,9 @@ func ManifestRemove(w http.ResponseWriter, r *http.Request) { errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) return } - newImage, err := runtime.ImageRuntime().NewFromLocal(name) + manifestList, err := runtime.LibimageRuntime().LookupManifestList(name) if err != nil { - utils.ImageNotFound(w, name, err) + utils.Error(w, "Something went wrong.", http.StatusNotFound, err) return } d, err := digest.Parse(query.Digest) @@ -150,13 +147,13 @@ func ManifestRemove(w http.ResponseWriter, r *http.Request) { utils.Error(w, "invalid digest", http.StatusBadRequest, err) return } - newID, err := newImage.RemoveManifest(d) - if err != nil { + if err := manifestList.RemoveInstance(d); err != nil { utils.InternalServerError(w, err) return } - utils.WriteResponse(w, http.StatusOK, handlers.IDResponse{ID: newID}) + utils.WriteResponse(w, http.StatusOK, handlers.IDResponse{ID: manifestList.ID()}) } + func ManifestPush(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value("runtime").(*libpod.Runtime) decoder := r.Context().Value("decoder").(*schema.Decoder) diff --git a/pkg/api/handlers/libpod/system.go b/pkg/api/handlers/libpod/system.go index 2b4cef1bb..bca92a4af 100644 --- a/pkg/api/handlers/libpod/system.go +++ b/pkg/api/handlers/libpod/system.go @@ -4,21 +4,19 @@ import ( "net/http" "github.com/containers/podman/v3/libpod" - "github.com/containers/podman/v3/pkg/api/handlers/compat" "github.com/containers/podman/v3/pkg/api/handlers/utils" "github.com/containers/podman/v3/pkg/domain/entities" "github.com/containers/podman/v3/pkg/domain/infra/abi" + "github.com/containers/podman/v3/pkg/util" "github.com/gorilla/schema" "github.com/pkg/errors" ) // SystemPrune removes unused data func SystemPrune(w http.ResponseWriter, r *http.Request) { - var ( - decoder = r.Context().Value("decoder").(*schema.Decoder) - runtime = r.Context().Value("runtime").(*libpod.Runtime) - systemPruneReport = new(entities.SystemPruneReport) - ) + decoder := r.Context().Value("decoder").(*schema.Decoder) + runtime := r.Context().Value("runtime").(*libpod.Runtime) + query := struct { All bool `schema:"all"` Volumes bool `schema:"volumes"` @@ -29,39 +27,27 @@ func SystemPrune(w http.ResponseWriter, r *http.Request) { errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) return } - - podPruneReport, err := PodPruneHelper(r) + filterMap, err := util.PrepareFilters(r) if err != nil { - utils.InternalServerError(w, err) + utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, + errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) return } - systemPruneReport.PodPruneReport = podPruneReport - // We could parallelize this, should we? - containerPruneReports, err := compat.PruneContainersHelper(r, nil) - if err != nil { - utils.InternalServerError(w, err) - return - } - systemPruneReport.ContainerPruneReports = containerPruneReports + containerEngine := abi.ContainerEngine{Libpod: runtime} - imagePruneReports, err := runtime.ImageRuntime().PruneImages(r.Context(), query.All, nil) + pruneOptions := entities.SystemPruneOptions{ + All: query.All, + Volume: query.Volumes, + Filters: *filterMap, + } + report, err := containerEngine.SystemPrune(r.Context(), pruneOptions) if err != nil { utils.InternalServerError(w, err) return } - systemPruneReport.ImagePruneReports = imagePruneReports - - if query.Volumes { - volumePruneReports, err := pruneVolumesHelper(r) - if err != nil { - utils.InternalServerError(w, err) - return - } - systemPruneReport.VolumePruneReports = volumePruneReports - } - utils.WriteResponse(w, http.StatusOK, systemPruneReport) + utils.WriteResponse(w, http.StatusOK, report) } func DiskUsage(w http.ResponseWriter, r *http.Request) { diff --git a/pkg/api/handlers/swagger/swagger.go b/pkg/api/handlers/swagger/swagger.go index 384e06cac..ef3d12df8 100644 --- a/pkg/api/handlers/swagger/swagger.go +++ b/pkg/api/handlers/swagger/swagger.go @@ -2,7 +2,6 @@ package swagger import ( "github.com/containers/podman/v3/libpod/define" - "github.com/containers/podman/v3/libpod/image" "github.com/containers/podman/v3/pkg/api/handlers" "github.com/containers/podman/v3/pkg/domain/entities" "github.com/containers/podman/v3/pkg/inspect" @@ -66,7 +65,10 @@ type swagLibpodPlayKubeResponse struct { // swagger:response DocsImageDeleteResponse type swagImageDeleteResponse struct { // in:body - Body []image.ImageDeleteResponse + Body []struct { + Untagged []string `json:"untagged"` + Deleted string `json:"deleted"` + } } // Search results @@ -74,7 +76,20 @@ type swagImageDeleteResponse struct { type swagSearchResponse struct { // in:body Body struct { - image.SearchResult + // Index is the image index (e.g., "docker.io" or "quay.io") + Index string + // Name is the canonical name of the image (e.g., "docker.io/library/alpine"). + Name string + // Description of the image. + Description string + // Stars is the number of stars of the image. + Stars int + // Official indicates if it's an official image. + Official string + // Automated indicates if the image was created by an automated build. + Automated string + // Tag is the image tag + Tag string } } diff --git a/pkg/api/handlers/types.go b/pkg/api/handlers/types.go index 736203171..52d7633af 100644 --- a/pkg/api/handlers/types.go +++ b/pkg/api/handlers/types.go @@ -2,12 +2,9 @@ package handlers import ( "context" - "encoding/json" - "fmt" "time" - "github.com/containers/image/v5/manifest" - libpodImage "github.com/containers/podman/v3/libpod/image" + "github.com/containers/common/libimage" "github.com/containers/podman/v3/pkg/domain/entities" docker "github.com/docker/docker/api/types" dockerContainer "github.com/docker/docker/api/types/container" @@ -173,8 +170,8 @@ type ExecStartConfig struct { Tty bool `json:"Tty"` } -func ImageToImageSummary(l *libpodImage.Image) (*entities.ImageSummary, error) { - imageData, err := l.Inspect(context.TODO()) +func ImageToImageSummary(l *libimage.Image) (*entities.ImageSummary, error) { + imageData, err := l.Inspect(context.TODO(), true) if err != nil { return nil, errors.Wrapf(err, "failed to obtain summary for image %s", l.ID()) } @@ -197,17 +194,17 @@ func ImageToImageSummary(l *libpodImage.Image) (*entities.ImageSummary, error) { Labels: imageData.Labels, Containers: containerCount, ReadOnly: l.IsReadOnly(), - Dangling: l.Dangling(), + Dangling: l.IsDangling(), Names: l.Names(), Digest: string(imageData.Digest), - ConfigDigest: string(l.ConfigDigest), + ConfigDigest: "", // TODO: libpod/image didn't set it but libimage should History: imageData.NamesHistory, } return &is, nil } -func ImageDataToImageInspect(ctx context.Context, l *libpodImage.Image) (*ImageInspect, error) { - info, err := l.Inspect(context.Background()) +func ImageDataToImageInspect(ctx context.Context, l *libimage.Image) (*ImageInspect, error) { + info, err := l.Inspect(context.Background(), true) if err != nil { return nil, err } @@ -216,37 +213,17 @@ func ImageDataToImageInspect(ctx context.Context, l *libpodImage.Image) (*ImageI return nil, err } - // TODO the rest of these still need wiring! + // TODO: many fields in Config still need wiring config := dockerContainer.Config{ - // Hostname: "", - // Domainname: "", - User: info.User, - // AttachStdin: false, - // AttachStdout: false, - // AttachStderr: false, + User: info.User, ExposedPorts: ports, - // Tty: false, - // OpenStdin: false, - // StdinOnce: false, - Env: info.Config.Env, - Cmd: info.Config.Cmd, - // Healthcheck: l.ImageData.HealthCheck, - // ArgsEscaped: false, - // Image: "", - Volumes: info.Config.Volumes, - WorkingDir: info.Config.WorkingDir, - Entrypoint: info.Config.Entrypoint, - // NetworkDisabled: false, - // MacAddress: "", - // OnBuild: info.Config.OnBuild, - Labels: info.Labels, - StopSignal: info.Config.StopSignal, - // StopTimeout: nil, - // Shell: nil, - } - ic, err := l.ToImageRef(ctx) - if err != nil { - return nil, err + Env: info.Config.Env, + Cmd: info.Config.Cmd, + Volumes: info.Config.Volumes, + WorkingDir: info.Config.WorkingDir, + Entrypoint: info.Config.Entrypoint, + Labels: info.Labels, + StopSignal: info.Config.StopSignal, } rootfs := docker.RootFS{} @@ -257,6 +234,11 @@ func ImageDataToImageInspect(ctx context.Context, l *libpodImage.Image) (*ImageI rootfs.Layers = append(rootfs.Layers, string(layer)) } } + + graphDriver := docker.GraphDriverData{ + Name: info.GraphDriver.Name, + Data: info.GraphDriver.Data, + } dockerImageInspect := docker.ImageInspect{ Architecture: info.Architecture, Author: info.Author, @@ -264,8 +246,8 @@ func ImageDataToImageInspect(ctx context.Context, l *libpodImage.Image) (*ImageI Config: &config, Created: l.Created().Format(time.RFC3339Nano), DockerVersion: info.Version, - GraphDriver: docker.GraphDriverData{}, - ID: fmt.Sprintf("sha256:%s", l.ID()), + GraphDriver: graphDriver, + ID: "sha256:" + l.ID(), Metadata: docker.ImageMetadata{}, Os: info.Os, OsVersion: info.Version, @@ -277,33 +259,7 @@ func ImageDataToImageInspect(ctx context.Context, l *libpodImage.Image) (*ImageI Variant: "", VirtualSize: info.VirtualSize, } - bi := ic.ConfigInfo() - // For docker images, we need to get the Container id and config - // and populate the image with it. - if bi.MediaType == manifest.DockerV2Schema2ConfigMediaType { - d := manifest.Schema2Image{} - b, err := ic.ConfigBlob(ctx) - if err != nil { - return nil, err - } - if err := json.Unmarshal(b, &d); err != nil { - return nil, err - } - // populate the Container id into the image - dockerImageInspect.Container = d.Container - containerConfig := dockerContainer.Config{} - configBytes, err := json.Marshal(d.ContainerConfig) - if err != nil { - return nil, err - } - if err := json.Unmarshal(configBytes, &containerConfig); err != nil { - return nil, err - } - // populate the Container config in the image - dockerImageInspect.ContainerConfig = &containerConfig - // populate parent - dockerImageInspect.Parent = d.Parent.String() - } + // TODO: consider filling the container config. return &ImageInspect{dockerImageInspect}, nil } diff --git a/pkg/api/handlers/utils/errors.go b/pkg/api/handlers/utils/errors.go index d22ad414f..4a8005bfd 100644 --- a/pkg/api/handlers/utils/errors.go +++ b/pkg/api/handlers/utils/errors.go @@ -6,6 +6,7 @@ import ( "github.com/containers/podman/v3/libpod/define" "github.com/containers/podman/v3/pkg/errorhandling" + "github.com/containers/storage" "github.com/pkg/errors" log "github.com/sirupsen/logrus" ) @@ -49,7 +50,7 @@ func ContainerNotFound(w http.ResponseWriter, name string, err error) { } func ImageNotFound(w http.ResponseWriter, name string, err error) { - if errors.Cause(err) != define.ErrNoSuchImage { + if errors.Cause(err) != storage.ErrImageUnknown { InternalServerError(w, err) } msg := fmt.Sprintf("No such image: %s", name) diff --git a/pkg/api/handlers/utils/images.go b/pkg/api/handlers/utils/images.go index da3c9e985..2662cd368 100644 --- a/pkg/api/handlers/utils/images.go +++ b/pkg/api/handlers/utils/images.go @@ -3,15 +3,14 @@ package utils import ( "fmt" "net/http" - "strings" + "github.com/containers/common/libimage" + "github.com/containers/common/pkg/filters" "github.com/containers/image/v5/docker" "github.com/containers/image/v5/storage" "github.com/containers/image/v5/transports/alltransports" "github.com/containers/image/v5/types" "github.com/containers/podman/v3/libpod" - "github.com/containers/podman/v3/libpod/image" - "github.com/containers/podman/v3/pkg/util" "github.com/gorilla/schema" "github.com/pkg/errors" ) @@ -54,7 +53,7 @@ func ParseStorageReference(name string) (types.ImageReference, error) { // GetImages is a common function used to get images for libpod and other compatibility // mechanisms -func GetImages(w http.ResponseWriter, r *http.Request) ([]*image.Image, error) { +func GetImages(w http.ResponseWriter, r *http.Request) ([]*libimage.Image, error) { decoder := r.Context().Value("decoder").(*schema.Decoder) runtime := r.Context().Value("runtime").(*libpod.Runtime) query := struct { @@ -65,56 +64,37 @@ func GetImages(w http.ResponseWriter, r *http.Request) ([]*image.Image, error) { // This is where you can override the golang default value for one of fields } - filterMap, err := util.PrepareFilters(r) - if err != nil { - return nil, err - } - if err := decoder.Decode(&query, r.URL.Query()); err != nil { return nil, err } - var filters = []string{} if _, found := r.URL.Query()["digests"]; found && query.Digests { UnSupportedParameter("digests") } - var images []*image.Image - queryFilters := *filterMap + filterList, err := filters.FiltersFromRequest(r) + if err != nil { + return nil, err + } if !IsLibpodRequest(r) && len(query.Filter) > 0 { // Docker 1.24 compatibility - if queryFilters == nil { - queryFilters = make(map[string][]string) - } - queryFilters["reference"] = append(queryFilters["reference"], query.Filter) + filterList = append(filterList, "reference="+query.Filter) } - if len(queryFilters) > 0 { - for k, v := range queryFilters { - filters = append(filters, fmt.Sprintf("%s=%s", k, strings.Join(v, "="))) - } - images, err = runtime.ImageRuntime().GetImagesWithFilters(filters) - if err != nil { - return images, err - } - } else { - images, err = runtime.ImageRuntime().GetImages() - if err != nil { - return images, err - } - } - if query.All { - return images, nil + if !query.All { + // Filter intermediate images unless we want to list *all*. + // NOTE: it's a positive filter, so `intermediate=false` means + // to display non-intermediate images. + filterList = append(filterList, "intermediate=false") } + listOptions := &libimage.ListImagesOptions{Filters: filterList} + return runtime.LibimageRuntime().ListImages(r.Context(), nil, listOptions) +} - filter, err := runtime.ImageRuntime().IntermediateFilter(r.Context(), images) +func GetImage(r *http.Request, name string) (*libimage.Image, error) { + runtime := r.Context().Value("runtime").(*libpod.Runtime) + lookupOptions := &libimage.LookupImageOptions{IgnorePlatform: true} + image, _, err := runtime.LibimageRuntime().LookupImage(name, lookupOptions) if err != nil { return nil, err } - images = image.FilterImages(images, []image.ResultFilter{filter}) - - return images, nil -} - -func GetImage(r *http.Request, name string) (*image.Image, error) { - runtime := r.Context().Value("runtime").(*libpod.Runtime) - return runtime.ImageRuntime().NewFromLocal(name) + return image, err } diff --git a/pkg/autoupdate/autoupdate.go b/pkg/autoupdate/autoupdate.go index e4b43109f..0a13e7e74 100644 --- a/pkg/autoupdate/autoupdate.go +++ b/pkg/autoupdate/autoupdate.go @@ -5,16 +5,16 @@ import ( "os" "sort" + "github.com/containers/common/libimage" + "github.com/containers/common/pkg/config" "github.com/containers/image/v5/docker" "github.com/containers/image/v5/docker/reference" "github.com/containers/image/v5/manifest" "github.com/containers/image/v5/transports/alltransports" "github.com/containers/podman/v3/libpod" "github.com/containers/podman/v3/libpod/define" - "github.com/containers/podman/v3/libpod/image" "github.com/containers/podman/v3/pkg/systemd" systemdDefine "github.com/containers/podman/v3/pkg/systemd/define" - "github.com/containers/podman/v3/pkg/util" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -126,12 +126,15 @@ func AutoUpdate(runtime *libpod.Runtime, options Options) ([]string, []error) { return nil, errs } - // Create a map from `image ID -> *image.Image` for image lookups. - imagesSlice, err := runtime.ImageRuntime().GetImages() + // Create a map from `image ID -> *libimage.Image` for image lookups. + listOptions := &libimage.ListImagesOptions{ + Filters: []string{"readonly=false"}, + } + imagesSlice, err := runtime.LibimageRuntime().ListImages(context.Background(), nil, listOptions) if err != nil { return nil, []error{err} } - imageMap := make(map[string]*image.Image) + imageMap := make(map[string]*libimage.Image) for i := range imagesSlice { imageMap[imagesSlice[i].ID()] = imagesSlice[i] } @@ -190,7 +193,7 @@ func AutoUpdate(runtime *libpod.Runtime, options Options) ([]string, []error) { errs = append(errs, errors.Errorf("error locally auto-updating container %q: raw-image name is empty", cid)) } // This avoids restarting containers unnecessarily. - needsUpdate, err := newerLocalImageAvailable(image, rawImageName) + needsUpdate, err := newerLocalImageAvailable(runtime, image, rawImageName) if err != nil { errs = append(errs, errors.Wrapf(err, "error locally auto-updating container %q: image check for %q failed", cid, rawImageName)) continue @@ -288,13 +291,13 @@ func readAuthenticationPath(ctr *libpod.Container, options Options) { // newerRemoteImageAvailable returns true if there corresponding image on the remote // registry is newer. -func newerRemoteImageAvailable(runtime *libpod.Runtime, img *image.Image, origName string, options Options) (bool, error) { +func newerRemoteImageAvailable(runtime *libpod.Runtime, img *libimage.Image, origName string, options Options) (bool, error) { remoteRef, err := docker.ParseReference("//" + origName) if err != nil { return false, err } - data, err := img.Inspect(context.Background()) + data, err := img.Inspect(context.Background(), false) if err != nil { return false, err } @@ -326,13 +329,8 @@ func newerRemoteImageAvailable(runtime *libpod.Runtime, img *image.Image, origNa } // newerLocalImageAvailable returns true if the container and local image have different digests -func newerLocalImageAvailable(img *image.Image, rawImageName string) (bool, error) { - rt, err := libpod.NewRuntime(context.TODO()) - if err != nil { - return false, err - } - - localImg, err := rt.ImageRuntime().NewFromLocal(rawImageName) +func newerLocalImageAvailable(runtime *libpod.Runtime, img *libimage.Image, rawImageName string) (bool, error) { + localImg, _, err := runtime.LibimageRuntime().LookupImage(rawImageName, nil) if err != nil { return false, err } @@ -345,31 +343,14 @@ func newerLocalImageAvailable(img *image.Image, rawImageName string) (bool, erro } // updateImage pulls the specified image. -func updateImage(runtime *libpod.Runtime, name string, options Options) (*image.Image, error) { - sys := runtime.SystemContext() - registryOpts := image.DockerRegistryOptions{} - signaturePolicyPath := "" - - if sys != nil { - registryOpts.OSChoice = sys.OSChoice - registryOpts.ArchitectureChoice = sys.OSChoice - registryOpts.DockerCertPath = sys.DockerCertPath - signaturePolicyPath = sys.SignaturePolicyPath - } +func updateImage(runtime *libpod.Runtime, name string, options Options) (*libimage.Image, error) { + pullOptions := &libimage.PullOptions{} + pullOptions.AuthFilePath = options.Authfile + pullOptions.Writer = os.Stderr - newImage, err := runtime.ImageRuntime().New(context.Background(), - docker.Transport.Name()+"://"+name, - signaturePolicyPath, - options.Authfile, - os.Stderr, - ®istryOpts, - image.SigningOptions{}, - nil, - util.PullImageAlways, - nil, - ) + pulledImages, err := runtime.LibimageRuntime().Pull(context.Background(), name, config.PullPolicyAlways, pullOptions) if err != nil { return nil, err } - return newImage, nil + return pulledImages[0], nil } diff --git a/pkg/bindings/images/pull.go b/pkg/bindings/images/pull.go index f4da2d521..9780c3bff 100644 --- a/pkg/bindings/images/pull.go +++ b/pkg/bindings/images/pull.go @@ -3,7 +3,6 @@ package images import ( "context" "encoding/json" - "errors" "fmt" "io" "io/ioutil" @@ -15,6 +14,7 @@ import ( "github.com/containers/podman/v3/pkg/bindings" "github.com/containers/podman/v3/pkg/domain/entities" "github.com/hashicorp/go-multierror" + "github.com/pkg/errors" ) // Pull is the binding for libpod's v2 endpoints for pulling images. Note that @@ -91,7 +91,7 @@ func Pull(ctx context.Context, rawImage string, options *PullOptions) ([]string, images = report.Images case report.ID != "": default: - return images, errors.New("failed to parse pull results stream, unexpected input") + return images, errors.Errorf("failed to parse pull results stream, unexpected input: %v", report) } } return images, mErr diff --git a/pkg/bindings/test/images_test.go b/pkg/bindings/test/images_test.go index 688bf049f..ff8f72c85 100644 --- a/pkg/bindings/test/images_test.go +++ b/pkg/bindings/test/images_test.go @@ -9,7 +9,6 @@ import ( "github.com/containers/podman/v3/pkg/bindings" "github.com/containers/podman/v3/pkg/bindings/containers" "github.com/containers/podman/v3/pkg/bindings/images" - dreports "github.com/containers/podman/v3/pkg/domain/entities/reports" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" "github.com/onsi/gomega/gexec" @@ -116,7 +115,7 @@ var _ = Describe("Podman images", func() { // Removing the image "alpine" where force = true options := new(images.RemoveOptions).WithForce(true) response, errs = images.Remove(bt.conn, []string{alpine.shortName}, options) - Expect(len(errs)).To(BeZero()) + Expect(errs).To(BeNil()) // To be extra sure, check if the previously created container // is gone as well. _, err = containers.Inspect(bt.conn, "top", nil) @@ -346,7 +345,6 @@ var _ = Describe("Podman images", func() { results, err := images.Prune(bt.conn, options) Expect(err).NotTo(HaveOccurred()) Expect(len(results)).To(BeNumerically(">", 0)) - Expect(dreports.PruneReportsIds(results)).To(ContainElement("docker.io/library/alpine:latest")) }) // TODO: we really need to extent to pull tests once we have a more sophisticated CI. diff --git a/pkg/bindings/test/system_test.go b/pkg/bindings/test/system_test.go index 68e9d9301..aecc5db81 100644 --- a/pkg/bindings/test/system_test.go +++ b/pkg/bindings/test/system_test.go @@ -83,8 +83,6 @@ var _ = Describe("Podman system", func() { Expect(len(systemPruneResponse.ContainerPruneReports)).To(Equal(1)) Expect(len(systemPruneResponse.ImagePruneReports)). To(BeNumerically(">", 0)) - Expect(reports.PruneReportsIds(systemPruneResponse.ImagePruneReports)). - To(ContainElement("docker.io/library/alpine:latest")) Expect(len(systemPruneResponse.VolumePruneReports)).To(Equal(0)) }) @@ -193,13 +191,8 @@ var _ = Describe("Podman system", func() { systemPruneResponse, err := system.Prune(bt.conn, options) Expect(err).To(BeNil()) Expect(len(systemPruneResponse.PodPruneReport)).To(Equal(0)) - // TODO fix system filter handling so all components can handle filters - // This check **should** be "Equal(0)" since we are passing label - // filters however the Prune function doesn't seem to pass filters - // to each component. - Expect(len(systemPruneResponse.ContainerPruneReports)).To(Equal(1)) - Expect(len(systemPruneResponse.ImagePruneReports)). - To(BeNumerically(">", 0)) + Expect(len(systemPruneResponse.ContainerPruneReports)).To(Equal(0)) + Expect(len(systemPruneResponse.ImagePruneReports)).To(Equal(0)) // Alpine image should not be pruned as used by running container Expect(reports.PruneReportsIds(systemPruneResponse.ImagePruneReports)). ToNot(ContainElement("docker.io/library/alpine:latest")) diff --git a/pkg/checkpoint/checkpoint_restore.go b/pkg/checkpoint/checkpoint_restore.go index 77a993128..7a8f71c66 100644 --- a/pkg/checkpoint/checkpoint_restore.go +++ b/pkg/checkpoint/checkpoint_restore.go @@ -6,11 +6,11 @@ import ( "os" metadata "github.com/checkpoint-restore/checkpointctl/lib" + "github.com/containers/common/libimage" + "github.com/containers/common/pkg/config" "github.com/containers/podman/v3/libpod" - "github.com/containers/podman/v3/libpod/image" "github.com/containers/podman/v3/pkg/domain/entities" "github.com/containers/podman/v3/pkg/errorhandling" - "github.com/containers/podman/v3/pkg/util" "github.com/containers/storage/pkg/archive" spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" @@ -62,19 +62,19 @@ func CRImportCheckpoint(ctx context.Context, runtime *libpod.Runtime, restoreOpt } // Load config.dump from temporary directory - config := new(libpod.ContainerConfig) - if _, err = metadata.ReadJSONFile(config, dir, metadata.ConfigDumpFile); err != nil { + ctrConfig := new(libpod.ContainerConfig) + if _, err = metadata.ReadJSONFile(ctrConfig, dir, metadata.ConfigDumpFile); err != nil { return nil, err } // This should not happen as checkpoints with these options are not exported. - if len(config.Dependencies) > 0 { + if len(ctrConfig.Dependencies) > 0 { return nil, errors.Errorf("Cannot import checkpoints of containers with dependencies") } // Volumes included in the checkpoint should not exist if !restoreOptions.IgnoreVolumes { - for _, vol := range config.NamedVolumes { + for _, vol := range ctrConfig.NamedVolumes { exists, err := runtime.HasVolume(vol.Name) if err != nil { return nil, err @@ -85,33 +85,24 @@ func CRImportCheckpoint(ctx context.Context, runtime *libpod.Runtime, restoreOpt } } - ctrID := config.ID + ctrID := ctrConfig.ID newName := false // Check if the restored container gets a new name if restoreOptions.Name != "" { - config.ID = "" - config.Name = restoreOptions.Name + ctrConfig.ID = "" + ctrConfig.Name = restoreOptions.Name newName = true } - ctrName := config.Name - - // The code to load the images is copied from create.go - // In create.go this only set if '--quiet' does not exist. - writer := os.Stderr - rtc, err := runtime.GetConfig() - if err != nil { - return nil, err - } - - _, err = runtime.ImageRuntime().New(ctx, config.RootfsImageName, rtc.Engine.SignaturePolicyPath, "", writer, nil, image.SigningOptions{}, nil, util.PullImageMissing, nil) - if err != nil { + pullOptions := &libimage.PullOptions{} + pullOptions.Writer = os.Stderr + if _, err := runtime.LibimageRuntime().Pull(ctx, ctrConfig.RootfsImageName, config.PullPolicyMissing, pullOptions); err != nil { return nil, err } // Now create a new container from the just loaded information - container, err := runtime.RestoreContainer(ctx, dumpSpec, config) + container, err := runtime.RestoreContainer(ctx, dumpSpec, ctrConfig) if err != nil { return nil, err } @@ -122,6 +113,7 @@ func CRImportCheckpoint(ctx context.Context, runtime *libpod.Runtime, restoreOpt } containerConfig := container.Config() + ctrName := ctrConfig.Name if containerConfig.Name != ctrName { return nil, errors.Errorf("Name of restored container (%s) does not match requested name (%s)", containerConfig.Name, ctrName) } diff --git a/pkg/domain/entities/manifest.go b/pkg/domain/entities/manifest.go index 6a645e20b..3f89e4d30 100644 --- a/pkg/domain/entities/manifest.go +++ b/pkg/domain/entities/manifest.go @@ -8,6 +8,7 @@ type ManifestCreateOptions struct { All bool `schema:"all"` } +// swagger:model ManifestAddOpts type ManifestAddOptions struct { All bool `json:"all" schema:"all"` Annotation []string `json:"annotation" schema:"annotation"` diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go index 82f2a2424..ef3ccab0c 100644 --- a/pkg/domain/infra/abi/containers.go +++ b/pkg/domain/infra/abi/containers.go @@ -15,7 +15,6 @@ import ( "github.com/containers/podman/v3/libpod" "github.com/containers/podman/v3/libpod/define" "github.com/containers/podman/v3/libpod/events" - "github.com/containers/podman/v3/libpod/image" "github.com/containers/podman/v3/libpod/logs" "github.com/containers/podman/v3/pkg/cgroups" "github.com/containers/podman/v3/pkg/checkpoint" @@ -23,6 +22,7 @@ import ( "github.com/containers/podman/v3/pkg/domain/entities/reports" dfilters "github.com/containers/podman/v3/pkg/domain/filters" "github.com/containers/podman/v3/pkg/domain/infra/abi/terminal" + "github.com/containers/podman/v3/pkg/errorhandling" parallelctr "github.com/containers/podman/v3/pkg/parallel/ctr" "github.com/containers/podman/v3/pkg/ps" "github.com/containers/podman/v3/pkg/rootless" @@ -438,7 +438,8 @@ func (ic *ContainerEngine) ContainerCommit(ctx context.Context, nameOrID string, default: return nil, errors.Errorf("unrecognized image format %q", options.Format) } - sc := image.GetSystemContext(rtc.Engine.SignaturePolicyPath, "", false) + + sc := ic.Libpod.SystemContext() coptions := buildah.CommitOptions{ SignaturePolicyPath: rtc.Engine.SignaturePolicyPath, ReportWriter: options.Writer, @@ -999,14 +1000,9 @@ func (ic *ContainerEngine) ContainerCleanup(ctx context.Context, namesOrIds []st if options.RemoveImage { _, imageName := ctr.Image() - ctrImage, err := ic.Libpod.ImageRuntime().NewFromLocal(imageName) - if err != nil { - report.RmiErr = err - reports = append(reports, &report) - continue - } - _, err = ic.Libpod.RemoveImage(ctx, ctrImage, false) - report.RmiErr = err + imageEngine := ImageEngine{Libpod: ic.Libpod} + _, rmErrors := imageEngine.Remove(ctx, []string{imageName}, entities.ImageRemoveOptions{}) + report.RmiErr = errorhandling.JoinErrors(rmErrors) } reports = append(reports, &report) diff --git a/pkg/domain/infra/abi/containers_runlabel.go b/pkg/domain/infra/abi/containers_runlabel.go index 2cabab988..199ae43ad 100644 --- a/pkg/domain/infra/abi/containers_runlabel.go +++ b/pkg/domain/infra/abi/containers_runlabel.go @@ -7,8 +7,9 @@ import ( "path/filepath" "strings" + "github.com/containers/common/libimage" + "github.com/containers/common/pkg/config" "github.com/containers/podman/v3/libpod/define" - "github.com/containers/podman/v3/libpod/image" "github.com/containers/podman/v3/pkg/domain/entities" envLib "github.com/containers/podman/v3/pkg/env" "github.com/containers/podman/v3/utils" @@ -18,21 +19,48 @@ import ( ) func (ic *ContainerEngine) ContainerRunlabel(ctx context.Context, label string, imageRef string, args []string, options entities.ContainerRunlabelOptions) error { - // First, get the image and pull it if needed. - img, err := ic.runlabelImage(ctx, label, imageRef, options) + pullOptions := &libimage.PullOptions{} + pullOptions.AuthFilePath = options.Authfile + pullOptions.CertDirPath = options.CertDir + pullOptions.Credentials = options.Credentials + pullOptions.SignaturePolicyPath = options.SignaturePolicy + pullOptions.InsecureSkipTLSVerify = options.SkipTLSVerify + + pullPolicy := config.PullPolicyNever + if options.Pull { + pullPolicy = config.PullPolicyMissing + } + if !options.Quiet { + pullOptions.Writer = os.Stderr + } + + pulledImages, err := ic.Libpod.LibimageRuntime().Pull(ctx, imageRef, pullPolicy, pullOptions) if err != nil { return err } + + if len(pulledImages) != 1 { + return errors.Errorf("internal error: expected an image to be pulled (or an error)") + } + // Extract the runlabel from the image. - runlabel, err := img.GetLabel(ctx, label) + labels, err := pulledImages[0].Labels(ctx) if err != nil { return err } + + var runlabel string + for k, v := range labels { + if strings.EqualFold(k, label) { + runlabel = v + break + } + } if runlabel == "" { return errors.Errorf("cannot find the value of label: %s in image: %s", label, imageRef) } - cmd, env, err := generateRunlabelCommand(runlabel, img, args, options) + cmd, env, err := generateRunlabelCommand(runlabel, pulledImages[0], imageRef, args, options) if err != nil { return err } @@ -76,36 +104,9 @@ func (ic *ContainerEngine) ContainerRunlabel(ctx context.Context, label string, return utils.ExecCmdWithStdStreams(stdIn, stdOut, stdErr, env, cmd[0], cmd[1:]...) } -// runlabelImage returns an image based on the specified image AND options. -func (ic *ContainerEngine) runlabelImage(ctx context.Context, label string, imageRef string, options entities.ContainerRunlabelOptions) (*image.Image, error) { - // First, look up the image locally. If we get an error and requested - // to pull, fallthrough and pull it. - img, err := ic.Libpod.ImageRuntime().NewFromLocal(imageRef) - switch { - case err == nil: - return img, nil - case !options.Pull: - return nil, err - default: - // Fallthrough and pull! - } - - pullOptions := entities.ImagePullOptions{ - Quiet: options.Quiet, - CertDir: options.CertDir, - SkipTLSVerify: options.SkipTLSVerify, - SignaturePolicy: options.SignaturePolicy, - Authfile: options.Authfile, - } - if _, err := pull(ctx, ic.Libpod.ImageRuntime(), imageRef, pullOptions, &label); err != nil { - return nil, err - } - return ic.Libpod.ImageRuntime().NewFromLocal(imageRef) -} - // generateRunlabelCommand generates the to-be-executed command as a string // slice along with a base environment. -func generateRunlabelCommand(runlabel string, img *image.Image, args []string, options entities.ContainerRunlabelOptions) ([]string, []string, error) { +func generateRunlabelCommand(runlabel string, img *libimage.Image, inputName string, args []string, options entities.ContainerRunlabelOptions) ([]string, []string, error) { var ( err error name, imageName string @@ -113,24 +114,25 @@ func generateRunlabelCommand(runlabel string, img *image.Image, args []string, o cmd []string ) - // TODO: How do we get global opts as done in v1? - // Extract the imageName (or ID). - imgNames := img.Names() + imgNames := img.NamesHistory() if len(imgNames) == 0 { imageName = img.ID() } else { + // The newest name is the first entry in the `NamesHistory` + // slice. imageName = imgNames[0] } // Use the user-specified name or extract one from the image. - if options.Name != "" { - name = options.Name - } else { - name, err = image.GetImageBaseName(imageName) - if err != nil { - return nil, nil, err + name = options.Name + if name == "" { + normalize := imageName + if !strings.HasPrefix(img.ID(), inputName) { + normalize = inputName } + splitImageName := strings.Split(normalize, "/") + name = splitImageName[len(splitImageName)-1] } // Append the user-specified arguments to the runlabel (command). diff --git a/pkg/domain/infra/abi/images.go b/pkg/domain/infra/abi/images.go index 84c7ebecd..0364b00a3 100644 --- a/pkg/domain/infra/abi/images.go +++ b/pkg/domain/infra/abi/images.go @@ -3,15 +3,14 @@ package abi import ( "context" "fmt" - "io" "io/ioutil" "net/url" "os" "path" "path/filepath" "strconv" - "strings" + "github.com/containers/common/libimage" "github.com/containers/common/pkg/config" "github.com/containers/image/v5/docker" "github.com/containers/image/v5/docker/reference" @@ -19,14 +18,11 @@ import ( "github.com/containers/image/v5/signature" "github.com/containers/image/v5/transports" "github.com/containers/image/v5/transports/alltransports" - "github.com/containers/image/v5/types" - "github.com/containers/podman/v3/libpod/define" - "github.com/containers/podman/v3/libpod/image" "github.com/containers/podman/v3/pkg/domain/entities" "github.com/containers/podman/v3/pkg/domain/entities/reports" domainUtils "github.com/containers/podman/v3/pkg/domain/utils" + "github.com/containers/podman/v3/pkg/errorhandling" "github.com/containers/podman/v3/pkg/rootless" - "github.com/containers/podman/v3/pkg/util" "github.com/containers/storage" dockerRef "github.com/docker/distribution/reference" "github.com/opencontainers/go-digest" @@ -36,31 +32,84 @@ import ( ) func (ir *ImageEngine) Exists(_ context.Context, nameOrID string) (*entities.BoolReport, error) { - _, err := ir.Libpod.ImageRuntime().NewFromLocal(nameOrID) + exists, err := ir.Libpod.LibimageRuntime().Exists(nameOrID) if err != nil { - if errors.Cause(err) == define.ErrMultipleImages { - return &entities.BoolReport{Value: true}, nil - } - if errors.Cause(err) != define.ErrNoSuchImage { - return nil, err - } + return nil, err } - return &entities.BoolReport{Value: err == nil}, nil + return &entities.BoolReport{Value: exists}, nil } func (ir *ImageEngine) Prune(ctx context.Context, opts entities.ImagePruneOptions) ([]*reports.PruneReport, error) { - reports, err := ir.Libpod.ImageRuntime().PruneImages(ctx, opts.All, opts.Filter) - if err != nil { - return nil, err + // NOTE: the terms "dangling" and "intermediate" are not used + // consistently across our code base. In libimage, "dangling" means + // that an image has no tags. "intermediate" means that an image is + // dangling and that no other image depends on it (i.e., has no + // children). + // + // While pruning usually refers to "dangling" images, it has always + // removed "intermediate" ones. + defaultOptions := &libimage.RemoveImagesOptions{ + Filters: append(opts.Filter, "intermediate=true", "containers=false", "readonly=false"), + WithSize: true, + } + + // `image prune --all` means to *also* remove images which are not in + // use by any container. Since image filters are chained, we need to + // do two look ups since the default ones are a subset of all. + unusedOptions := &libimage.RemoveImagesOptions{ + Filters: append(opts.Filter, "containers=false", "readonly=false"), + WithSize: true, + } + + var pruneReports []*reports.PruneReport + + // Now prune all images until we converge. + numPreviouslyRemovedImages := 1 + for { + removedDefault, rmErrors := ir.Libpod.LibimageRuntime().RemoveImages(ctx, nil, defaultOptions) + if rmErrors != nil { + return nil, errorhandling.JoinErrors(rmErrors) + } + removedUnused, rmErrors := ir.Libpod.LibimageRuntime().RemoveImages(ctx, nil, unusedOptions) + if rmErrors != nil { + return nil, errorhandling.JoinErrors(rmErrors) + } + + for _, rmReport := range append(removedDefault, removedUnused...) { + r := *rmReport + pruneReports = append(pruneReports, &reports.PruneReport{ + Id: r.ID, + Size: uint64(r.Size), + }) + } + + numRemovedImages := len(removedDefault) + len(removedUnused) + if numRemovedImages+numPreviouslyRemovedImages == 0 { + break + } + numPreviouslyRemovedImages = numRemovedImages } - return reports, err + + return pruneReports, nil +} + +func toDomainHistoryLayer(layer *libimage.ImageHistory) entities.ImageHistoryLayer { + l := entities.ImageHistoryLayer{} + l.ID = layer.ID + l.Created = *layer.Created + l.CreatedBy = layer.CreatedBy + copy(l.Tags, layer.Tags) + l.Size = layer.Size + l.Comment = layer.Comment + return l } func (ir *ImageEngine) History(ctx context.Context, nameOrID string, opts entities.ImageHistoryOptions) (*entities.ImageHistoryReport, error) { - image, err := ir.Libpod.ImageRuntime().NewFromLocal(nameOrID) + image, _, err := ir.Libpod.LibimageRuntime().LookupImage(nameOrID, &libimage.LookupImageOptions{IgnorePlatform: true}) if err != nil { return nil, err } + results, err := image.History(ctx) if err != nil { return nil, err @@ -70,17 +119,17 @@ func (ir *ImageEngine) History(ctx context.Context, nameOrID string, opts entiti Layers: make([]entities.ImageHistoryLayer, len(results)), } - for i, layer := range results { - history.Layers[i] = ToDomainHistoryLayer(layer) + for i := range results { + history.Layers[i] = toDomainHistoryLayer(&results[i]) } return &history, nil } func (ir *ImageEngine) Mount(ctx context.Context, nameOrIDs []string, opts entities.ImageMountOptions) ([]*entities.ImageMountReport, error) { - var ( - images []*image.Image - err error - ) + if opts.All && len(nameOrIDs) > 0 { + return nil, errors.Errorf("cannot mix --all with images") + } + if os.Geteuid() != 0 { if driver := ir.Libpod.StorageConfig().GraphDriverName; driver != "vfs" { // Do not allow to mount a graphdriver that is not vfs if we are creating the userns as part @@ -96,219 +145,129 @@ func (ir *ImageEngine) Mount(ctx context.Context, nameOrIDs []string, opts entit os.Exit(ret) } } + + listImagesOptions := &libimage.ListImagesOptions{} if opts.All { - allImages, err := ir.Libpod.ImageRuntime().GetImages() - if err != nil { - return nil, err - } - for _, img := range allImages { - if !img.IsReadOnly() { - images = append(images, img) - } - } - } else { - for _, i := range nameOrIDs { - img, err := ir.Libpod.ImageRuntime().NewFromLocal(i) - if err != nil { - return nil, err - } - images = append(images, img) - } + listImagesOptions.Filters = []string{"readonly=false"} } - reports := make([]*entities.ImageMountReport, 0, len(images)) - for _, img := range images { - report := entities.ImageMountReport{Id: img.ID()} - if img.IsReadOnly() { - report.Err = errors.Errorf("mounting readonly %s image not supported", img.ID()) - } else { - report.Path, report.Err = img.Mount([]string{}, "") - } - reports = append(reports, &report) - } - if len(reports) > 0 { - return reports, nil - } - - images, err = ir.Libpod.ImageRuntime().GetImages() + images, err := ir.Libpod.LibimageRuntime().ListImages(ctx, nameOrIDs, listImagesOptions) if err != nil { return nil, err } + + mountReports := []*entities.ImageMountReport{} + listMountsOnly := !opts.All && len(nameOrIDs) == 0 for _, i := range images { - mounted, path, err := i.Mounted() - if err != nil { - if errors.Cause(err) == storage.ErrLayerUnknown { - continue - } - return nil, err - } - if mounted { - tags, err := i.RepoTags() + // TODO: the .Err fields are not used. This pre-dates the + // libimage migration but should be addressed at some point. + // A quick glimpse at cmd/podman/image/mount.go suggests that + // the errors needed to be handled there as well. + var mountPoint string + var err error + if listMountsOnly { + // We're only looking for mounted images. + mountPoint, err = i.Mountpoint() if err != nil { return nil, err } - reports = append(reports, &entities.ImageMountReport{ - Id: i.ID(), - Name: string(i.Digest()), - Repositories: tags, - Path: path, - }) - } - } - return reports, nil -} - -func (ir *ImageEngine) Unmount(ctx context.Context, nameOrIDs []string, options entities.ImageUnmountOptions) ([]*entities.ImageUnmountReport, error) { - var images []*image.Image - - if options.All { - allImages, err := ir.Libpod.ImageRuntime().GetImages() - if err != nil { - return nil, err - } - for _, img := range allImages { - if !img.IsReadOnly() { - images = append(images, img) + // Not mounted, so skip. + if mountPoint == "" { + continue } - } - } else { - for _, i := range nameOrIDs { - img, err := ir.Libpod.ImageRuntime().NewFromLocal(i) + } else { + mountPoint, err = i.Mount(ctx, nil, "") if err != nil { return nil, err } - images = append(images, img) } - } - reports := []*entities.ImageUnmountReport{} - for _, img := range images { - report := entities.ImageUnmountReport{Id: img.ID()} - mounted, _, err := img.Mounted() + tags, err := i.RepoTags() if err != nil { - // Errors will be caught in Unmount call below - // Default assumption to mounted - mounted = true - } - if !mounted { - continue - } - if err := img.Unmount(options.Force); err != nil { - if options.All && errors.Cause(err) == storage.ErrLayerNotMounted { - logrus.Debugf("Error umounting image %s, storage.ErrLayerNotMounted", img.ID()) - continue - } - report.Err = errors.Wrapf(err, "error unmounting image %s", img.ID()) + return nil, err } - reports = append(reports, &report) + mountReports = append(mountReports, &entities.ImageMountReport{ + Id: i.ID(), + Name: string(i.Digest()), + Repositories: tags, + Path: mountPoint, + }) } - return reports, nil + return mountReports, nil } -func ToDomainHistoryLayer(layer *image.History) entities.ImageHistoryLayer { - l := entities.ImageHistoryLayer{} - l.ID = layer.ID - l.Created = *layer.Created - l.CreatedBy = layer.CreatedBy - copy(l.Tags, layer.Tags) - l.Size = layer.Size - l.Comment = layer.Comment - return l -} - -func pull(ctx context.Context, runtime *image.Runtime, rawImage string, options entities.ImagePullOptions, label *string) (*entities.ImagePullReport, error) { - var writer io.Writer - if !options.Quiet { - writer = os.Stderr - } - - dockerPrefix := fmt.Sprintf("%s://", docker.Transport.Name()) - imageRef, err := alltransports.ParseImageName(rawImage) - if err != nil { - imageRef, err = alltransports.ParseImageName(fmt.Sprintf("%s%s", dockerPrefix, rawImage)) - if err != nil { - return nil, errors.Wrapf(err, "invalid image reference %q", rawImage) - } +func (ir *ImageEngine) Unmount(ctx context.Context, nameOrIDs []string, options entities.ImageUnmountOptions) ([]*entities.ImageUnmountReport, error) { + if options.All && len(nameOrIDs) > 0 { + return nil, errors.Errorf("cannot mix --all with images") } - var registryCreds *types.DockerAuthConfig - if len(options.Username) > 0 && len(options.Password) > 0 { - registryCreds = &types.DockerAuthConfig{ - Username: options.Username, - Password: options.Password, - } + listImagesOptions := &libimage.ListImagesOptions{} + if options.All { + listImagesOptions.Filters = []string{"readonly=false"} } - dockerRegistryOptions := image.DockerRegistryOptions{ - DockerRegistryCreds: registryCreds, - DockerCertPath: options.CertDir, - OSChoice: options.OS, - ArchitectureChoice: options.Arch, - VariantChoice: options.Variant, - DockerInsecureSkipTLSVerify: options.SkipTLSVerify, + images, err := ir.Libpod.LibimageRuntime().ListImages(ctx, nameOrIDs, listImagesOptions) + if err != nil { + return nil, err } - if !options.AllTags { - newImage, err := runtime.New(ctx, rawImage, options.SignaturePolicy, options.Authfile, writer, &dockerRegistryOptions, image.SigningOptions{}, label, options.PullPolicy, nil) + unmountReports := []*entities.ImageUnmountReport{} + for _, image := range images { + r := &entities.ImageUnmountReport{Id: image.ID()} + mountPoint, err := image.Mountpoint() if err != nil { - return nil, err + r.Err = err + unmountReports = append(unmountReports, r) + continue } - return &entities.ImagePullReport{Images: []string{newImage.ID()}}, nil - } - - // --all-tags requires the docker transport - if imageRef.Transport().Name() != docker.Transport.Name() { - return nil, errors.New("--all-tags requires docker transport") + if mountPoint == "" { + // Skip if the image wasn't mounted. + continue + } + r.Err = image.Unmount(options.Force) + unmountReports = append(unmountReports, r) } + return unmountReports, nil +} - // Trim the docker-transport prefix. - rawImage = strings.TrimPrefix(rawImage, docker.Transport.Name()) +func (ir *ImageEngine) Pull(ctx context.Context, rawImage string, options entities.ImagePullOptions) (*entities.ImagePullReport, error) { + pullOptions := &libimage.PullOptions{AllTags: options.AllTags} + pullOptions.AuthFilePath = options.Authfile + pullOptions.CertDirPath = options.CertDir + pullOptions.Username = options.Username + pullOptions.Password = options.Password + pullOptions.Architecture = options.Arch + pullOptions.OS = options.OS + pullOptions.Variant = options.Variant + pullOptions.SignaturePolicyPath = options.SignaturePolicy + pullOptions.InsecureSkipTLSVerify = options.SkipTLSVerify - // all-tags doesn't work with a tagged reference, so let's check early - namedRef, err := reference.Parse(rawImage) - if err != nil { - return nil, errors.Wrapf(err, "error parsing %q", rawImage) - } - if _, isTagged := namedRef.(reference.Tagged); isTagged { - return nil, errors.New("--all-tags requires a reference without a tag") + if !options.Quiet { + pullOptions.Writer = os.Stderr } - systemContext := image.GetSystemContext("", options.Authfile, false) - tags, err := docker.GetRepositoryTags(ctx, systemContext, imageRef) + pulledImages, err := ir.Libpod.LibimageRuntime().Pull(ctx, rawImage, options.PullPolicy, pullOptions) if err != nil { - return nil, errors.Wrapf(err, "error getting repository tags") + return nil, err } - foundIDs := []string{} - for _, tag := range tags { - name := rawImage + ":" + tag - newImage, err := runtime.New(ctx, name, options.SignaturePolicy, options.Authfile, writer, &dockerRegistryOptions, image.SigningOptions{}, nil, util.PullImageAlways, nil) - if err != nil { - logrus.Errorf("error pulling image %q", name) - continue - } - foundIDs = append(foundIDs, newImage.ID()) + pulledIDs := make([]string, len(pulledImages)) + for i := range pulledImages { + pulledIDs[i] = pulledImages[i].ID() } - if len(tags) != len(foundIDs) { - return nil, err - } - return &entities.ImagePullReport{Images: foundIDs}, nil -} - -func (ir *ImageEngine) Pull(ctx context.Context, rawImage string, options entities.ImagePullOptions) (*entities.ImagePullReport, error) { - return pull(ctx, ir.Libpod.ImageRuntime(), rawImage, options, nil) + return &entities.ImagePullReport{Images: pulledIDs}, nil } func (ir *ImageEngine) Inspect(ctx context.Context, namesOrIDs []string, opts entities.InspectOptions) ([]*entities.ImageInspectReport, []error, error) { reports := []*entities.ImageInspectReport{} errs := []error{} for _, i := range namesOrIDs { - img, err := ir.Libpod.ImageRuntime().NewFromLocal(i) + img, _, err := ir.Libpod.LibimageRuntime().LookupImage(i, &libimage.LookupImageOptions{IgnorePlatform: true}) if err != nil { // This is probably a no such image, treat as nonfatal. errs = append(errs, err) continue } - result, err := img.Inspect(ctx) + result, err := img.Inspect(ctx, true) if err != nil { // This is more likely to be fatal. return nil, nil, err @@ -323,11 +282,6 @@ func (ir *ImageEngine) Inspect(ctx context.Context, namesOrIDs []string, opts en } func (ir *ImageEngine) Push(ctx context.Context, source string, destination string, options entities.ImagePushOptions) error { - var writer io.Writer - if !options.Quiet { - writer = os.Stderr - } - var manifestType string switch options.Format { case "": @@ -342,58 +296,56 @@ func (ir *ImageEngine) Push(ctx context.Context, source string, destination stri return errors.Errorf("unknown format %q. Choose on of the supported formats: 'oci', 'v2s1', or 'v2s2'", options.Format) } - var registryCreds *types.DockerAuthConfig - if len(options.Username) > 0 && len(options.Password) > 0 { - registryCreds = &types.DockerAuthConfig{ - Username: options.Username, - Password: options.Password, - } - } - dockerRegistryOptions := image.DockerRegistryOptions{ - DockerRegistryCreds: registryCreds, - DockerCertPath: options.CertDir, - DockerInsecureSkipTLSVerify: options.SkipTLSVerify, - } + pushOptions := &libimage.PushOptions{} + pushOptions.AuthFilePath = options.Authfile + pushOptions.CertDirPath = options.CertDir + pushOptions.DirForceCompress = options.Compress + pushOptions.Username = options.Username + pushOptions.Password = options.Password + pushOptions.ManifestMIMEType = manifestType + pushOptions.RemoveSignatures = options.RemoveSignatures + pushOptions.SignBy = options.SignBy + pushOptions.InsecureSkipTLSVerify = options.SkipTLSVerify - signOptions := image.SigningOptions{ - RemoveSignatures: options.RemoveSignatures, - SignBy: options.SignBy, + if !options.Quiet { + pushOptions.Writer = os.Stderr } - newImage, err := ir.Libpod.ImageRuntime().NewFromLocal(source) - if err != nil { - return err - } + pushedManifestBytes, pushError := ir.Libpod.LibimageRuntime().Push(ctx, source, destination, pushOptions) + if pushError == nil { + if options.DigestFile != "" { + manifestDigest, err := manifest.Digest(pushedManifestBytes) + if err != nil { + return err + } - err = newImage.PushImageToHeuristicDestination( - ctx, - destination, - manifestType, - options.Authfile, - options.DigestFile, - options.SignaturePolicy, - writer, - options.Compress, - signOptions, - &dockerRegistryOptions, - nil, - options.Progress) - if err != nil && errors.Cause(err) != storage.ErrImageUnknown { + if err := ioutil.WriteFile(options.DigestFile, []byte(manifestDigest.String()), 0644); err != nil { + return err + } + } + return nil + } + // If the image could not be found, we may be referring to a manifest + // list but could not find a matching image instance in the local + // containers storage. In that case, fall back and attempt to push the + // (entire) manifest. + if errors.Cause(pushError) == storage.ErrImageUnknown { // Image might be a manifest list so attempt a manifest push - if _, manifestErr := ir.ManifestPush(ctx, source, destination, options); manifestErr == nil { + _, manifestErr := ir.ManifestPush(ctx, source, destination, options) + if manifestErr == nil { return nil } } - return err + return pushError } func (ir *ImageEngine) Tag(ctx context.Context, nameOrID string, tags []string, options entities.ImageTagOptions) error { - newImage, err := ir.Libpod.ImageRuntime().NewFromLocal(nameOrID) + image, _, err := ir.Libpod.LibimageRuntime().LookupImage(nameOrID, &libimage.LookupImageOptions{IgnorePlatform: true}) if err != nil { return err } for _, tag := range tags { - if err := newImage.TagImage(tag); err != nil { + if err := image.Tag(tag); err != nil { return err } } @@ -401,54 +353,71 @@ func (ir *ImageEngine) Tag(ctx context.Context, nameOrID string, tags []string, } func (ir *ImageEngine) Untag(ctx context.Context, nameOrID string, tags []string, options entities.ImageUntagOptions) error { - newImage, err := ir.Libpod.ImageRuntime().NewFromLocal(nameOrID) + image, _, err := ir.Libpod.LibimageRuntime().LookupImage(nameOrID, &libimage.LookupImageOptions{IgnorePlatform: true}) if err != nil { return err } // If only one arg is provided, all names are to be untagged if len(tags) == 0 { - tags = newImage.Names() + tags = image.Names() } for _, tag := range tags { - if err := newImage.UntagImage(tag); err != nil { + if err := image.Untag(tag); err != nil { return err } } return nil } -func (ir *ImageEngine) Load(ctx context.Context, opts entities.ImageLoadOptions) (*entities.ImageLoadReport, error) { - var ( - writer io.Writer - ) - if !opts.Quiet { - writer = os.Stderr - } - name, err := ir.Libpod.LoadImage(ctx, opts.Input, writer, opts.SignaturePolicy) - if err != nil { - return nil, err +func (ir *ImageEngine) Load(ctx context.Context, options entities.ImageLoadOptions) (*entities.ImageLoadReport, error) { + loadOptions := &libimage.LoadOptions{} + loadOptions.SignaturePolicyPath = options.SignaturePolicy + if !options.Quiet { + loadOptions.Writer = os.Stderr } - return &entities.ImageLoadReport{Names: strings.Split(name, ",")}, nil -} -func (ir *ImageEngine) Import(ctx context.Context, opts entities.ImageImportOptions) (*entities.ImageImportReport, error) { - id, err := ir.Libpod.Import(ctx, opts.Source, opts.Reference, opts.SignaturePolicy, opts.Changes, opts.Message, opts.Quiet) + loadedImages, err := ir.Libpod.LibimageRuntime().Load(ctx, options.Input, loadOptions) if err != nil { return nil, err } - return &entities.ImageImportReport{Id: id}, nil + return &entities.ImageLoadReport{Names: loadedImages}, nil } func (ir *ImageEngine) Save(ctx context.Context, nameOrID string, tags []string, options entities.ImageSaveOptions) error { + saveOptions := &libimage.SaveOptions{} + saveOptions.DirForceCompress = options.Compress + saveOptions.RemoveSignatures = options.RemoveSignatures + + if !options.Quiet { + saveOptions.Writer = os.Stderr + } + + names := []string{nameOrID} if options.MultiImageArchive { - nameOrIDs := append([]string{nameOrID}, tags...) - return ir.Libpod.ImageRuntime().SaveImages(ctx, nameOrIDs, options.Format, options.Output, options.Quiet, true) + names = append(names, tags...) + } else { + saveOptions.AdditionalTags = tags } - newImage, err := ir.Libpod.ImageRuntime().NewFromLocal(nameOrID) + return ir.Libpod.LibimageRuntime().Save(ctx, names, options.Format, options.Output, saveOptions) +} + +func (ir *ImageEngine) Import(ctx context.Context, options entities.ImageImportOptions) (*entities.ImageImportReport, error) { + importOptions := &libimage.ImportOptions{} + importOptions.Changes = options.Changes + importOptions.CommitMessage = options.Message + importOptions.Tag = options.Reference + importOptions.SignaturePolicyPath = options.SignaturePolicy + + if !options.Quiet { + importOptions.Writer = os.Stderr + } + + imageID, err := ir.Libpod.LibimageRuntime().Import(ctx, options.Source, importOptions) if err != nil { - return err + return nil, err } - return newImage.Save(ctx, nameOrID, options.Format, options.Output, tags, options.Quiet, options.Compress, true) + + return &entities.ImageImportReport{Id: imageID}, nil } func (ir *ImageEngine) Diff(_ context.Context, nameOrID string, _ entities.DiffOptions) (*entities.DiffReport, error) { @@ -460,12 +429,12 @@ func (ir *ImageEngine) Diff(_ context.Context, nameOrID string, _ entities.DiffO } func (ir *ImageEngine) Search(ctx context.Context, term string, opts entities.ImageSearchOptions) ([]entities.ImageSearchReport, error) { - filter, err := image.ParseSearchFilter(opts.Filters) + filter, err := libimage.ParseSearchFilter(opts.Filters) if err != nil { return nil, err } - searchOpts := image.SearchOptions{ + searchOptions := &libimage.SearchOptions{ Authfile: opts.Authfile, Filter: *filter, Limit: opts.Limit, @@ -474,7 +443,7 @@ func (ir *ImageEngine) Search(ctx context.Context, term string, opts entities.Im ListTags: opts.ListTags, } - searchResults, err := image.SearchImages(term, searchOpts) + searchResults, err := ir.Libpod.LibimageRuntime().Search(ctx, term, searchOptions) if err != nil { return nil, err } @@ -510,15 +479,15 @@ func (ir *ImageEngine) Build(ctx context.Context, containerFiles []string, opts } func (ir *ImageEngine) Tree(ctx context.Context, nameOrID string, opts entities.ImageTreeOptions) (*entities.ImageTreeReport, error) { - img, err := ir.Libpod.ImageRuntime().NewFromLocal(nameOrID) + image, _, err := ir.Libpod.LibimageRuntime().LookupImage(nameOrID, &libimage.LookupImageOptions{IgnorePlatform: true}) if err != nil { return nil, err } - results, err := img.GenerateTree(opts.WhatRequires) + tree, err := image.Tree(opts.WhatRequires) if err != nil { return nil, err } - return &entities.ImageTreeReport{Tree: results}, nil + return &entities.ImageTreeReport{Tree: tree}, nil } // removeErrorsToExitCode returns an exit code for the specified slice of @@ -542,9 +511,9 @@ func removeErrorsToExitCode(rmErrors []error) int { for _, e := range rmErrors { switch errors.Cause(e) { - case define.ErrNoSuchImage: + case storage.ErrImageUnknown, storage.ErrLayerUnknown: noSuchImageErrors = true - case define.ErrImageInUse, storage.ErrImageUsedByContainer: + case storage.ErrImageUsedByContainer: inUseErrors = true default: otherErrors = true @@ -574,84 +543,25 @@ func (ir *ImageEngine) Remove(ctx context.Context, images []string, opts entitie report.ExitCode = removeErrorsToExitCode(rmErrors) }() - // deleteImage is an anonymous function to conveniently delete an image - // without having to pass all local data around. - deleteImage := func(img *image.Image) error { - results, err := ir.Libpod.RemoveImage(ctx, img, opts.Force) - switch errors.Cause(err) { - case nil: - // Removal worked, so let's report it. - report.Deleted = append(report.Deleted, results.Deleted) - report.Untagged = append(report.Untagged, results.Untagged...) - return nil - case storage.ErrImageUnknown, storage.ErrLayerUnknown: - // The image must have been removed already (see #6510) - // or the storage is corrupted (see #9617). - report.Deleted = append(report.Deleted, img.ID()) - report.Untagged = append(report.Untagged, img.ID()) - return nil - default: - // Fatal error. - return err - } + libimageOptions := &libimage.RemoveImagesOptions{} + libimageOptions.Filters = []string{"readonly=false"} + libimageOptions.Force = opts.Force + if !opts.All { + libimageOptions.Filters = append(libimageOptions.Filters, "intermediate=false") } + libimageOptions.RemoveContainerFunc = ir.Libpod.RemoveContainersForImageCallback(ctx) - // Delete all images from the local storage. - if opts.All { - previousImages := 0 - // Remove all images one-by-one. - for { - storageImages, err := ir.Libpod.ImageRuntime().GetRWImages() - if err != nil { - rmErrors = append(rmErrors, err) - return - } - // No images (left) to remove, so we're done. - if len(storageImages) == 0 { - return - } - // Prevent infinity loops by making a delete-progress check. - if previousImages == len(storageImages) { - rmErrors = append(rmErrors, errors.New("unable to delete all images, check errors and re-run image removal if needed")) - break - } - previousImages = len(storageImages) - // Delete all "leaves" (i.e., images without child images). - for _, img := range storageImages { - isParent, err := img.IsParent(ctx) - if err != nil { - logrus.Warnf("%v, ignoring the error", err) - isParent = false - } - // Skip parent images. - if isParent { - continue - } - if err := deleteImage(img); err != nil { - rmErrors = append(rmErrors, err) - } - } - } - - return - } + libimageReport, libimageErrors := ir.Libpod.LibimageRuntime().RemoveImages(ctx, images, libimageOptions) - // Delete only the specified images. - for _, id := range images { - img, err := ir.Libpod.ImageRuntime().NewFromLocal(id) - if err != nil { - // attempt to remove image from storage - if forceErr := ir.Libpod.RemoveImageFromStorage(id); forceErr == nil { - continue - } - rmErrors = append(rmErrors, err) - continue - } - err = deleteImage(img) - if err != nil { - rmErrors = append(rmErrors, err) + for _, r := range libimageReport { + if r.Removed { + report.Deleted = append(report.Deleted, r.ID) } + report.Untagged = append(report.Untagged, r.Untagged...) } + + rmErrors = libimageErrors + return //nolint } diff --git a/pkg/domain/infra/abi/images_list.go b/pkg/domain/infra/abi/images_list.go index 3b8aabeb7..b0e947991 100644 --- a/pkg/domain/infra/abi/images_list.go +++ b/pkg/domain/infra/abi/images_list.go @@ -3,23 +3,25 @@ package abi import ( "context" - libpodImage "github.com/containers/podman/v3/libpod/image" + "github.com/containers/common/libimage" "github.com/containers/podman/v3/pkg/domain/entities" "github.com/pkg/errors" ) func (ir *ImageEngine) List(ctx context.Context, opts entities.ImageListOptions) ([]*entities.ImageSummary, error) { - images, err := ir.Libpod.ImageRuntime().GetImagesWithFilters(opts.Filter) - if err != nil { - return nil, err + listImagesOptions := &libimage.ListImagesOptions{ + Filters: opts.Filter, } - if !opts.All { - filter, err := ir.Libpod.ImageRuntime().IntermediateFilter(ctx, images) - if err != nil { - return nil, err - } - images = libpodImage.FilterImages(images, []libpodImage.ResultFilter{filter}) + // Filter intermediate images unless we want to list *all*. + // NOTE: it's a positive filter, so `intermediate=false` means + // to display non-intermediate images. + listImagesOptions.Filters = append(listImagesOptions.Filters, "intermediate=false") + } + + images, err := ir.Libpod.LibimageRuntime().ListImages(ctx, nil, listImagesOptions) + if err != nil { + return nil, err } summaries := []*entities.ImageSummary{} @@ -30,24 +32,21 @@ func (ir *ImageEngine) List(ctx context.Context, opts entities.ImageListOptions) } e := entities.ImageSummary{ - ID: img.ID(), - ConfigDigest: string(img.ConfigDigest), - Created: img.Created().Unix(), - Dangling: img.Dangling(), - Digest: string(img.Digest()), - RepoDigests: digests, - History: img.NamesHistory(), - Names: img.Names(), - ReadOnly: img.IsReadOnly(), - SharedSize: 0, - RepoTags: img.Names(), // may include tags and digests + ID: img.ID(), + // ConfigDigest: string(img.ConfigDigest), + Created: img.Created().Unix(), + Dangling: img.IsDangling(), + Digest: string(img.Digest()), + RepoDigests: digests, + History: img.NamesHistory(), + Names: img.Names(), + ReadOnly: img.IsReadOnly(), + SharedSize: 0, + RepoTags: img.Names(), // may include tags and digests } e.Labels, err = img.Labels(ctx) if err != nil { - // Ignore empty manifest lists. - if errors.Cause(err) != libpodImage.ErrImageIsBareList { - return nil, errors.Wrapf(err, "error retrieving label for image %q: you may need to remove the image to resolve the error", img.ID()) - } + return nil, errors.Wrapf(err, "error retrieving label for image %q: you may need to remove the image to resolve the error", img.ID()) } ctnrs, err := img.Containers() @@ -56,20 +55,22 @@ func (ir *ImageEngine) List(ctx context.Context, opts entities.ImageListOptions) } e.Containers = len(ctnrs) - sz, err := img.Size(ctx) + sz, err := img.Size() if err != nil { return nil, errors.Wrapf(err, "error retrieving size of image %q: you may need to remove the image to resolve the error", img.ID()) } - e.Size = int64(*sz) + e.Size = sz // This is good enough for now, but has to be // replaced later with correct calculation logic - e.VirtualSize = int64(*sz) + e.VirtualSize = sz - parent, err := img.ParentID(ctx) + parent, err := img.Parent(ctx) if err != nil { return nil, errors.Wrapf(err, "error retrieving parent of image %q: you may need to remove the image to resolve the error", img.ID()) } - e.ParentId = parent + if parent != nil { + e.ParentId = parent.ID() + } summaries = append(summaries, &e) } diff --git a/pkg/domain/infra/abi/manifest.go b/pkg/domain/infra/abi/manifest.go index 8e3c50fac..f932cf21d 100644 --- a/pkg/domain/infra/abi/manifest.go +++ b/pkg/domain/infra/abi/manifest.go @@ -4,21 +4,17 @@ import ( "bytes" "context" "encoding/json" - "fmt" "os" "strings" - "github.com/containers/buildah/manifests" - buildahManifests "github.com/containers/buildah/pkg/manifests" - buildahUtil "github.com/containers/buildah/util" + "github.com/containers/common/libimage" cp "github.com/containers/image/v5/copy" - "github.com/containers/image/v5/docker" "github.com/containers/image/v5/manifest" + "github.com/containers/image/v5/pkg/shortnames" "github.com/containers/image/v5/transports" "github.com/containers/image/v5/transports/alltransports" - "github.com/containers/image/v5/types" - libpodImage "github.com/containers/podman/v3/libpod/image" "github.com/containers/podman/v3/pkg/domain/entities" + "github.com/containers/storage" "github.com/opencontainers/go-digest" imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" @@ -26,51 +22,103 @@ import ( ) // ManifestCreate implements logic for creating manifest lists via ImageEngine -func (ir *ImageEngine) ManifestCreate(ctx context.Context, names, images []string, opts entities.ManifestCreateOptions) (string, error) { - fullNames, err := buildahUtil.ExpandNames(names, "", ir.Libpod.SystemContext(), ir.Libpod.GetStore()) - if err != nil { - return "", errors.Wrapf(err, "error encountered while expanding image name %q", names) +func (ir *ImageEngine) ManifestCreate(ctx context.Context, names []string, images []string, opts entities.ManifestCreateOptions) (string, error) { + // FIXME: change the interface of manifest create `names []string` -> + // `name string`. + if len(names) == 0 { + return "", errors.New("no name specified for creating a manifest list") } - imageID, err := libpodImage.CreateManifestList(ir.Libpod.ImageRuntime(), *ir.Libpod.SystemContext(), fullNames, images, opts.All) + name := names[0] + + manifestList, err := ir.Libpod.LibimageRuntime().CreateManifestList(name) if err != nil { - return imageID, err + return "", err } - return imageID, err + + addOptions := &libimage.ManifestListAddOptions{All: opts.All} + for _, image := range images { + if _, err := manifestList.Add(ctx, image, addOptions); err != nil { + return "", err + } + } + + return manifestList.ID(), nil } // ManifestExists checks if a manifest list with the given name exists in local storage func (ir *ImageEngine) ManifestExists(ctx context.Context, name string) (*entities.BoolReport, error) { - if image, err := ir.Libpod.ImageRuntime().NewFromLocal(name); err == nil { - exists, err := image.ExistsManifest() - if err != nil && errors.Cause(err) != buildahManifests.ErrManifestTypeNotSupported { - return nil, err + image, _, err := ir.Libpod.LibimageRuntime().LookupImage(name, &libimage.LookupImageOptions{IgnorePlatform: true}) + if err != nil { + if errors.Cause(err) == storage.ErrImageUnknown { + return &entities.BoolReport{Value: false}, nil } - return &entities.BoolReport{Value: exists}, nil + return nil, err + } + + isManifestList, err := image.IsManifestList(ctx) + if err != nil { + return nil, err } - return &entities.BoolReport{Value: false}, nil + return &entities.BoolReport{Value: isManifestList}, nil } // ManifestInspect returns the content of a manifest list or image func (ir *ImageEngine) ManifestInspect(ctx context.Context, name string) ([]byte, error) { - if newImage, err := ir.Libpod.ImageRuntime().NewFromLocal(name); err == nil { - // return the manifest in local storage - if list, err := newImage.InspectManifest(); err == nil { - buf, err := json.MarshalIndent(list, "", " ") - if err != nil { - return buf, errors.Wrapf(err, "error rendering manifest %s for display", name) - } - return buf, nil - // no return if local image is not a list of images type - // continue on getting valid manifest through remote service - } else if errors.Cause(err) != buildahManifests.ErrManifestTypeNotSupported { - return nil, errors.Wrapf(err, "loading manifest %q", name) + // NOTE: we have to do a bit of a limbo here as `podman manifest + // inspect foo` wants to do a remote-inspect of foo iff "foo" in the + // containers storage is an ordinary image but not a manifest list. + + lookupOptions := &libimage.LookupImageOptions{IgnorePlatform: true} + image, _, err := ir.Libpod.LibimageRuntime().LookupImage(name, lookupOptions) + if err != nil { + // If the image doesn't exist, do a remote inspect. + if errors.Cause(err) == storage.ErrImageUnknown { + return ir.remoteManifestInspect(ctx, name) } + return nil, err + } + + isManifestList, err := image.IsManifestList(ctx) + if err != nil { + return nil, err + } + + // If the image isn't a manifest list, do a remote inspect. + if !isManifestList { + return ir.remoteManifestInspect(ctx, name) } - sc := ir.Libpod.SystemContext() - refs, err := buildahUtil.ResolveNameToReferences(ir.Libpod.GetStore(), sc, name) + + manifestList, err := image.ToManifestList() if err != nil { return nil, err } + + schema2List, err := manifestList.Inspect() + if err != nil { + return nil, err + } + + rawSchema2List, err := json.Marshal(schema2List) + if err != nil { + return nil, err + } + + var b bytes.Buffer + if err := json.Indent(&b, rawSchema2List, "", " "); err != nil { + return nil, errors.Wrapf(err, "error rendering manifest %s for display", name) + } + return b.Bytes(), nil +} + +// inspect a remote manifest list. +func (ir *ImageEngine) remoteManifestInspect(ctx context.Context, name string) ([]byte, error) { + sys := ir.Libpod.SystemContext() + + resolved, err := shortnames.Resolve(sys, name) + if err != nil { + return nil, err + } + var ( latestErr error result []byte @@ -84,8 +132,13 @@ func (ir *ImageEngine) ManifestInspect(ctx context.Context, name string) ([]byte latestErr = errors.Wrapf(latestErr, "tried %v\n", e) } } - for _, ref := range refs { - src, err := ref.NewImageSource(ctx, sc) + + for _, candidate := range resolved.PullCandidates { + ref, err := alltransports.ParseImageName("docker://" + candidate.Value.String()) + if err != nil { + return nil, err + } + src, err := ref.NewImageSource(ctx, sys) if err != nil { appendErr(errors.Wrapf(err, "reading image %q", transports.ImageName(ref))) continue @@ -102,6 +155,7 @@ func (ir *ImageEngine) ManifestInspect(ctx context.Context, name string) ([]byte manType = manifestType break } + if len(result) == 0 && latestErr != nil { return nil, latestErr } @@ -138,29 +192,41 @@ func (ir *ImageEngine) ManifestInspect(ctx context.Context, name string) ([]byte // ManifestAdd adds images to the manifest list func (ir *ImageEngine) ManifestAdd(ctx context.Context, opts entities.ManifestAddOptions) (string, error) { - imageSpec := opts.Images[0] - listImageSpec := opts.Images[1] - dockerPrefix := fmt.Sprintf("%s://", docker.Transport.Name()) - _, err := alltransports.ParseImageName(imageSpec) + // FIXME: the name options below are *mandatory* arguments and should + // be reflected as such in the signature. + + if len(opts.Images) < 2 { + return "", errors.New("manifest add requires two images") + } + + imageName := opts.Images[0] + listName := opts.Images[1] + + manifestList, err := ir.Libpod.LibimageRuntime().LookupManifestList(listName) if err != nil { - _, err = alltransports.ParseImageName(fmt.Sprintf("%s%s", dockerPrefix, imageSpec)) - if err != nil { - return "", errors.Errorf("invalid image reference %q", imageSpec) - } + return "", err } - listImage, err := ir.Libpod.ImageRuntime().NewFromLocal(listImageSpec) + + addOptions := &libimage.ManifestListAddOptions{ + All: opts.All, + AuthFilePath: opts.Authfile, + CertDirPath: opts.CertDir, + InsecureSkipTLSVerify: opts.SkipTLSVerify, + Username: opts.Username, + Password: opts.Password, + } + + instanceDigest, err := manifestList.Add(ctx, imageName, addOptions) if err != nil { - return "", errors.Wrapf(err, "error retrieving local image from image name %s", listImageSpec) + return "", err } - manifestAddOpts := libpodImage.ManifestAddOpts{ - All: opts.All, - Arch: opts.Arch, - Features: opts.Features, - Images: opts.Images, - OS: opts.OS, - OSVersion: opts.OSVersion, - Variant: opts.Variant, + annotateOptions := &libimage.ManifestListAnnotateOptions{ + Architecture: opts.Arch, + Features: opts.Features, + OS: opts.OS, + OSVersion: opts.OSVersion, + Variant: opts.Variant, } if len(opts.Annotation) != 0 { annotations := make(map[string]string) @@ -171,51 +237,44 @@ func (ir *ImageEngine) ManifestAdd(ctx context.Context, opts entities.ManifestAd } annotations[spec[0]] = spec[1] } - manifestAddOpts.Annotation = annotations - } - - // Set the system context. - sys := ir.Libpod.SystemContext() - if sys != nil { - sys = &types.SystemContext{} + annotateOptions.Annotations = annotations } - sys.AuthFilePath = opts.Authfile - sys.DockerInsecureSkipTLSVerify = opts.SkipTLSVerify - sys.DockerCertPath = opts.CertDir - if opts.Username != "" && opts.Password != "" { - sys.DockerAuthConfig = &types.DockerAuthConfig{ - Username: opts.Username, - Password: opts.Password, - } + if err := manifestList.AnnotateInstance(instanceDigest, annotateOptions); err != nil { + return "", err } - listID, err := listImage.AddManifest(*sys, manifestAddOpts) - if err != nil { - return listID, err - } - return listID, nil + return manifestList.ID(), nil } // ManifestAnnotate updates an entry of the manifest list func (ir *ImageEngine) ManifestAnnotate(ctx context.Context, names []string, opts entities.ManifestAnnotateOptions) (string, error) { - listImage, err := ir.Libpod.ImageRuntime().NewFromLocal(names[0]) - if err != nil { - return "", errors.Wrapf(err, "error retrieving local image from image name %s", names[0]) + // FIXME: the `names` are *mandatory* arguments and should be + // reflected as such in the signature. + + if len(names) < 2 { + return "", errors.New("manifest annotate requires two names") } - digest, err := digest.Parse(names[1]) + + listName := names[0] + instanceDigest, err := digest.Parse(names[1]) if err != nil { return "", errors.Errorf(`invalid image digest "%s": %v`, names[1], err) } - manifestAnnotateOpts := libpodImage.ManifestAnnotateOpts{ - Arch: opts.Arch, - Features: opts.Features, - OS: opts.OS, - OSFeatures: opts.OSFeatures, - OSVersion: opts.OSVersion, - Variant: opts.Variant, + + manifestList, err := ir.Libpod.LibimageRuntime().LookupManifestList(listName) + if err != nil { + return "", err + } + + annotateOptions := &libimage.ManifestListAnnotateOptions{ + Architecture: opts.Arch, + Features: opts.Features, + OS: opts.OS, + OSVersion: opts.OSVersion, + Variant: opts.Variant, } - if len(opts.Annotation) > 0 { + if len(opts.Annotation) != 0 { annotations := make(map[string]string) for _, annotationSpec := range opts.Annotation { spec := strings.SplitN(annotationSpec, "=", 2) @@ -224,48 +283,49 @@ func (ir *ImageEngine) ManifestAnnotate(ctx context.Context, names []string, opt } annotations[spec[0]] = spec[1] } - manifestAnnotateOpts.Annotation = annotations + annotateOptions.Annotations = annotations } - updatedListID, err := listImage.AnnotateManifest(*ir.Libpod.SystemContext(), digest, manifestAnnotateOpts) - if err == nil { - return fmt.Sprintf("%s: %s", updatedListID, digest.String()), nil + + if err := manifestList.AnnotateInstance(instanceDigest, annotateOptions); err != nil { + return "", err } - return "", err + + return manifestList.ID(), nil } // ManifestRemove removes specified digest from the specified manifest list func (ir *ImageEngine) ManifestRemove(ctx context.Context, names []string) (string, error) { + // FIXME: the `names` are *mandatory* arguments and should be + // reflected as such in the signature. + + if len(names) < 2 { + return "", errors.New("manifest remove requires two names") + } + + listName := names[0] instanceDigest, err := digest.Parse(names[1]) if err != nil { return "", errors.Errorf(`invalid image digest "%s": %v`, names[1], err) } - listImage, err := ir.Libpod.ImageRuntime().NewFromLocal(names[0]) + + manifestList, err := ir.Libpod.LibimageRuntime().LookupManifestList(listName) if err != nil { - return "", errors.Wrapf(err, "error retrieving local image from image name %s", names[0]) + return "", err } - updatedListID, err := listImage.RemoveManifest(instanceDigest) - if err == nil { - return fmt.Sprintf("%s :%s\n", updatedListID, instanceDigest.String()), nil + + if err := manifestList.RemoveInstance(instanceDigest); err != nil { + return "", err } - return "", err + + return manifestList.ID(), nil } // ManifestPush pushes a manifest list or image index to the destination func (ir *ImageEngine) ManifestPush(ctx context.Context, name, destination string, opts entities.ImagePushOptions) (string, error) { - listImage, err := ir.Libpod.ImageRuntime().NewFromLocal(name) + manifestList, err := ir.Libpod.LibimageRuntime().LookupManifestList(name) if err != nil { return "", errors.Wrapf(err, "error retrieving local image from image name %s", name) } - dest, err := alltransports.ParseImageName(destination) - if err != nil { - oldErr := err - // Try adding the images default transport - destination2 := libpodImage.DefaultTransport + destination - dest, err = alltransports.ParseImageName(destination2) - if err != nil { - return "", oldErr - } - } var manifestType string if opts.Format != "" { @@ -279,40 +339,33 @@ func (ir *ImageEngine) ManifestPush(ctx context.Context, name, destination strin } } - // Set the system context. - sys := ir.Libpod.SystemContext() - if sys == nil { - sys = new(types.SystemContext) - } - sys.AuthFilePath = opts.Authfile - sys.DockerInsecureSkipTLSVerify = opts.SkipTLSVerify - sys.DockerCertPath = opts.CertDir - - if opts.Username != "" && opts.Password != "" { - sys.DockerAuthConfig = &types.DockerAuthConfig{ - Username: opts.Username, - Password: opts.Password, - } - } + pushOptions := &libimage.ManifestListPushOptions{} + pushOptions.AuthFilePath = opts.Authfile + pushOptions.CertDirPath = opts.CertDir + pushOptions.Username = opts.Username + pushOptions.Password = opts.Password + pushOptions.ImageListSelection = cp.CopySpecificImages + pushOptions.ManifestMIMEType = manifestType + pushOptions.RemoveSignatures = opts.RemoveSignatures + pushOptions.SignBy = opts.SignBy - options := manifests.PushOptions{ - Store: ir.Libpod.GetStore(), - SystemContext: sys, - ImageListSelection: cp.CopySpecificImages, - Instances: nil, - RemoveSignatures: opts.RemoveSignatures, - SignBy: opts.SignBy, - ManifestType: manifestType, - } if opts.All { - options.ImageListSelection = cp.CopyAllImages + pushOptions.ImageListSelection = cp.CopyAllImages } if !opts.Quiet { - options.ReportWriter = os.Stderr + pushOptions.Writer = os.Stderr } - manDigest, err := listImage.PushManifest(dest, options) - if err == nil && opts.Rm { - _, err = ir.Libpod.GetStore().DeleteImage(listImage.ID(), true) + + manDigest, err := manifestList.Push(ctx, destination, pushOptions) + if err != nil { + return "", err + } + + if opts.Rm { + if _, err := ir.Libpod.GetStore().DeleteImage(manifestList.ID(), true); err != nil { + return "", errors.Wrap(err, "error removing manifest after push") + } } + return manDigest.String(), err } diff --git a/pkg/domain/infra/abi/play.go b/pkg/domain/infra/abi/play.go index 6ddd4a042..d235c9ed8 100644 --- a/pkg/domain/infra/abi/play.go +++ b/pkg/domain/infra/abi/play.go @@ -10,17 +10,17 @@ import ( "strconv" "strings" + "github.com/containers/common/libimage" + "github.com/containers/common/pkg/config" "github.com/containers/common/pkg/secrets" "github.com/containers/image/v5/types" "github.com/containers/podman/v3/libpod" "github.com/containers/podman/v3/libpod/define" - "github.com/containers/podman/v3/libpod/image" "github.com/containers/podman/v3/pkg/domain/entities" "github.com/containers/podman/v3/pkg/specgen" "github.com/containers/podman/v3/pkg/specgen/generate" "github.com/containers/podman/v3/pkg/specgen/generate/kube" "github.com/containers/podman/v3/pkg/util" - "github.com/docker/distribution/reference" "github.com/ghodss/yaml" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -154,10 +154,9 @@ func (ic *ContainerEngine) playKubeDeployment(ctx context.Context, deploymentYAM func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podYAML *v1.PodTemplateSpec, options entities.PlayKubeOptions, ipIndex *int) (*entities.PlayKubeReport, error) { var ( - registryCreds *types.DockerAuthConfig - writer io.Writer - playKubePod entities.PlayKubePod - report entities.PlayKubeReport + writer io.Writer + playKubePod entities.PlayKubePod + report entities.PlayKubeReport ) // Create the secret manager before hand @@ -220,19 +219,6 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY writer = os.Stderr } - if len(options.Username) > 0 && len(options.Password) > 0 { - registryCreds = &types.DockerAuthConfig{ - Username: options.Username, - Password: options.Password, - } - } - - dockerRegistryOptions := image.DockerRegistryOptions{ - DockerRegistryCreds: registryCreds, - DockerCertPath: options.CertDir, - DockerInsecureSkipTLSVerify: options.SkipTLSVerify, - } - volumes, err := kube.InitializeVolumes(podYAML.Spec.Volumes) if err != nil { return nil, err @@ -273,35 +259,36 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY containers := make([]*libpod.Container, 0, len(podYAML.Spec.Containers)) for _, container := range podYAML.Spec.Containers { - pullPolicy := util.PullImageMissing + // NOTE: set the pull policy to "newer". This will cover cases + // where the "latest" tag requires a pull and will also + // transparently handle "localhost/" prefixed files which *may* + // refer to a locally built image OR an image running a + // registry on localhost. + pullPolicy := config.PullPolicyNewer if len(container.ImagePullPolicy) > 0 { - pullPolicy, err = util.ValidatePullType(string(container.ImagePullPolicy)) + pullPolicy, err = config.ParsePullPolicy(string(container.ImagePullPolicy)) if err != nil { return nil, err } } - named, err := reference.ParseNormalizedNamed(container.Image) - if err != nil { - return nil, errors.Wrapf(err, "Failed to parse image %q", container.Image) - } - // In kube, if the image is tagged with latest, it should always pull - // but if the domain is localhost, that means the image was built locally - // so do not attempt a pull. - if tagged, isTagged := named.(reference.NamedTagged); isTagged { - if tagged.Tag() == image.LatestTag && reference.Domain(named) != image.DefaultLocalRegistry { - pullPolicy = util.PullImageAlways - } - } - // This ensures the image is the image store - newImage, err := ic.Libpod.ImageRuntime().New(ctx, container.Image, options.SignaturePolicy, options.Authfile, writer, &dockerRegistryOptions, image.SigningOptions{}, nil, pullPolicy, nil) + pullOptions := &libimage.PullOptions{} + pullOptions.AuthFilePath = options.Authfile + pullOptions.CertDirPath = options.CertDir + pullOptions.SignaturePolicyPath = options.SignaturePolicy + pullOptions.Writer = writer + pullOptions.Username = options.Username + pullOptions.Password = options.Password + pullOptions.InsecureSkipTLSVerify = options.SkipTLSVerify + + pulledImages, err := ic.Libpod.LibimageRuntime().Pull(ctx, container.Image, pullPolicy, pullOptions) if err != nil { return nil, err } specgenOpts := kube.CtrSpecGenOptions{ Container: container, - Image: newImage, + Image: pulledImages[0], Volumes: volumes, PodID: pod.ID(), PodName: podName, diff --git a/pkg/domain/infra/abi/system.go b/pkg/domain/infra/abi/system.go index 9bba0fa6c..ebe59e871 100644 --- a/pkg/domain/infra/abi/system.go +++ b/pkg/domain/infra/abi/system.go @@ -164,7 +164,10 @@ func movePauseProcessToScope(r *libpod.Runtime) error { // SystemPrune removes unused data from the system. Pruning pods, containers, volumes and images. func (ic *ContainerEngine) SystemPrune(ctx context.Context, options entities.SystemPruneOptions) (*entities.SystemPruneReport, error) { var systemPruneReport = new(entities.SystemPruneReport) - var filters []string + filters := []string{} + for k, v := range options.Filters { + filters = append(filters, fmt.Sprintf("%s=%s", k, v[0])) + } reclaimedSpace := (uint64)(0) found := true for found { @@ -188,10 +191,12 @@ func (ic *ContainerEngine) SystemPrune(ctx context.Context, options entities.Sys } reclaimedSpace = reclaimedSpace + reports.PruneReportsSize(containerPruneReports) systemPruneReport.ContainerPruneReports = append(systemPruneReport.ContainerPruneReports, containerPruneReports...) - for k, v := range options.Filters { - filters = append(filters, fmt.Sprintf("%s=%s", k, v[0])) + imagePruneOptions := entities.ImagePruneOptions{ + All: options.All, + Filter: filters, } - imagePruneReports, err := ic.Libpod.ImageRuntime().PruneImages(ctx, options.All, filters) + imageEngine := ImageEngine{Libpod: ic.Libpod} + imagePruneReports, err := imageEngine.Prune(ctx, imagePruneOptions) reclaimedSpace = reclaimedSpace + reports.PruneReportsSize(imagePruneReports) if err != nil { @@ -225,13 +230,7 @@ func (ic *ContainerEngine) SystemDf(ctx context.Context, options entities.System dfImages = []*entities.SystemDfImageReport{} ) - // Compute disk-usage stats for all local images. - imgs, err := ic.Libpod.ImageRuntime().GetImages() - if err != nil { - return nil, err - } - - imageStats, err := ic.Libpod.ImageRuntime().DiskUsage(ctx, imgs) + imageStats, err := ic.Libpod.LibimageRuntime().DiskUsage(ctx) if err != nil { return nil, err } diff --git a/pkg/domain/infra/tunnel/images.go b/pkg/domain/infra/tunnel/images.go index 90b6e104b..3fd9a755d 100644 --- a/pkg/domain/infra/tunnel/images.go +++ b/pkg/domain/infra/tunnel/images.go @@ -8,10 +8,10 @@ import ( "strings" "time" + "github.com/containers/common/libimage" "github.com/containers/common/pkg/config" "github.com/containers/image/v5/docker/reference" "github.com/containers/image/v5/types" - "github.com/containers/podman/v3/libpod/image" images "github.com/containers/podman/v3/pkg/bindings/images" "github.com/containers/podman/v3/pkg/domain/entities" "github.com/containers/podman/v3/pkg/domain/entities/reports" @@ -311,7 +311,7 @@ func (ir *ImageEngine) Diff(ctx context.Context, nameOrID string, _ entities.Dif func (ir *ImageEngine) Search(ctx context.Context, term string, opts entities.ImageSearchOptions) ([]entities.ImageSearchReport, error) { mappedFilters := make(map[string][]string) - filters, err := image.ParseSearchFilter(opts.Filters) + filters, err := libimage.ParseSearchFilter(opts.Filters) if err != nil { return nil, err } diff --git a/pkg/errorhandling/errorhandling.go b/pkg/errorhandling/errorhandling.go index 9dc545ebb..9b1740006 100644 --- a/pkg/errorhandling/errorhandling.go +++ b/pkg/errorhandling/errorhandling.go @@ -33,6 +33,9 @@ func JoinErrors(errs []error) error { // ErrorsToString converts the slice of errors into a slice of corresponding // error messages. func ErrorsToStrings(errs []error) []string { + if len(errs) == 0 { + return nil + } strErrs := make([]string, len(errs)) for i := range errs { strErrs[i] = errs[i].Error() @@ -43,6 +46,9 @@ func ErrorsToStrings(errs []error) []string { // StringsToErrors converts a slice of error messages into a slice of // corresponding errors. func StringsToErrors(strErrs []string) []error { + if len(strErrs) == 0 { + return nil + } errs := make([]error, len(strErrs)) for i := range strErrs { errs[i] = errors.New(strErrs[i]) diff --git a/pkg/ps/ps.go b/pkg/ps/ps.go index b31978638..0b76636de 100644 --- a/pkg/ps/ps.go +++ b/pkg/ps/ps.go @@ -9,6 +9,7 @@ import ( "strings" "time" + "github.com/containers/common/libimage" "github.com/containers/podman/v3/libpod" "github.com/containers/podman/v3/libpod/define" "github.com/containers/podman/v3/pkg/domain/entities" @@ -257,12 +258,13 @@ func ListStorageContainer(rt *libpod.Runtime, ctr storage.Container, opts entiti imageName := "" if ctr.ImageID != "" { - names, err := rt.ImageRuntime().ImageNames(ctr.ImageID) + lookupOptions := &libimage.LookupImageOptions{IgnorePlatform: true} + image, _, err := rt.LibimageRuntime().LookupImage(ctr.ImageID, lookupOptions) if err != nil { return ps, err } - if len(names) > 0 { - imageName = names[0] + if len(image.NamesHistory()) > 0 { + imageName = image.NamesHistory()[0] } } else if buildahCtr { imageName = "scratch" diff --git a/pkg/specgen/config_unsupported.go b/pkg/specgen/config_unsupported.go index 3d89e49f8..70a60ac47 100644 --- a/pkg/specgen/config_unsupported.go +++ b/pkg/specgen/config_unsupported.go @@ -3,11 +3,11 @@ package specgen import ( - "github.com/containers/podman/v3/libpod/image" + "github.com/containers/common/libimage" spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" ) -func (s *SpecGenerator) getSeccompConfig(configSpec *spec.Spec, img *image.Image) (*spec.LinuxSeccomp, error) { +func (s *SpecGenerator) getSeccompConfig(configSpec *spec.Spec, img *libimage.Image) (*spec.LinuxSeccomp, error) { return nil, errors.New("function not supported on non-linux OS's") } diff --git a/pkg/specgen/generate/config_linux_cgo.go b/pkg/specgen/generate/config_linux_cgo.go index 41f03d5b6..6ffbf69c1 100644 --- a/pkg/specgen/generate/config_linux_cgo.go +++ b/pkg/specgen/generate/config_linux_cgo.go @@ -6,8 +6,8 @@ import ( "context" "io/ioutil" + "github.com/containers/common/libimage" goSeccomp "github.com/containers/common/pkg/seccomp" - "github.com/containers/podman/v3/libpod/image" "github.com/containers/podman/v3/pkg/seccomp" "github.com/containers/podman/v3/pkg/specgen" spec "github.com/opencontainers/runtime-spec/specs-go" @@ -15,7 +15,7 @@ import ( "github.com/sirupsen/logrus" ) -func getSeccompConfig(s *specgen.SpecGenerator, configSpec *spec.Spec, img *image.Image) (*spec.LinuxSeccomp, error) { +func getSeccompConfig(s *specgen.SpecGenerator, configSpec *spec.Spec, img *libimage.Image) (*spec.LinuxSeccomp, error) { var seccompConfig *spec.LinuxSeccomp var err error scp, err := seccomp.LookupPolicy(s.SeccompPolicy) diff --git a/pkg/specgen/generate/config_linux_nocgo.go b/pkg/specgen/generate/config_linux_nocgo.go index 0867988b6..4a1880b74 100644 --- a/pkg/specgen/generate/config_linux_nocgo.go +++ b/pkg/specgen/generate/config_linux_nocgo.go @@ -5,11 +5,11 @@ package generate import ( "errors" - "github.com/containers/podman/v3/libpod/image" + "github.com/containers/common/libimage" "github.com/containers/podman/v3/pkg/specgen" spec "github.com/opencontainers/runtime-spec/specs-go" ) -func getSeccompConfig(s *specgen.SpecGenerator, configSpec *spec.Spec, img *image.Image) (*spec.LinuxSeccomp, error) { +func getSeccompConfig(s *specgen.SpecGenerator, configSpec *spec.Spec, img *libimage.Image) (*spec.LinuxSeccomp, error) { return nil, errors.New("not implemented") } diff --git a/pkg/specgen/generate/container.go b/pkg/specgen/generate/container.go index 3d20ed8ff..d00e51e82 100644 --- a/pkg/specgen/generate/container.go +++ b/pkg/specgen/generate/container.go @@ -5,90 +5,45 @@ import ( "os" "strings" - "github.com/containers/image/v5/manifest" + "github.com/containers/common/libimage" "github.com/containers/podman/v3/libpod" - "github.com/containers/podman/v3/libpod/image" ann "github.com/containers/podman/v3/pkg/annotations" envLib "github.com/containers/podman/v3/pkg/env" "github.com/containers/podman/v3/pkg/signal" "github.com/containers/podman/v3/pkg/specgen" spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" - "github.com/sirupsen/logrus" "golang.org/x/sys/unix" ) // Fill any missing parts of the spec generator (e.g. from the image). // Returns a set of warnings or any fatal error that occurred. func CompleteSpec(ctx context.Context, r *libpod.Runtime, s *specgen.SpecGenerator) ([]string, error) { - var ( - newImage *image.Image - err error - ) - // Only add image configuration if we have an image + var newImage *libimage.Image + var inspectData *libimage.ImageData + var err error if s.Image != "" { - newImage, err = r.ImageRuntime().NewFromLocal(s.Image) + newImage, _, err = r.LibimageRuntime().LookupImage(s.Image, nil) if err != nil { return nil, err } - _, mediaType, err := newImage.Manifest(ctx) + inspectData, err = newImage.Inspect(ctx, false) if err != nil { - 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 - } + return nil, err } - if s.HealthConfig == nil && mediaType == manifest.DockerV2Schema2MediaType { - s.HealthConfig, err = newImage.GetHealthCheck(ctx) - if err != nil { - return nil, err - } + if s.HealthConfig == nil { + // NOTE: the health check is only set for Docker images + // but inspect will take care of it. + s.HealthConfig = inspectData.HealthCheck } // Image stop signal if s.StopSignal == nil { - stopSignal, err := newImage.StopSignal(ctx) - if err != nil { - return nil, err - } - if stopSignal != "" { - sig, err := signal.ParseSignalNameOrNumber(stopSignal) + if inspectData.Config.StopSignal != "" { + sig, err := signal.ParseSignalNameOrNumber(inspectData.Config.StopSignal) if err != nil { return nil, err } @@ -113,15 +68,10 @@ func CompleteSpec(ctx context.Context, r *libpod.Runtime, s *specgen.SpecGenerat var envs map[string]string // Image Environment defaults - if newImage != nil { + if inspectData != nil { // Image envs from the image if they don't exist // already, overriding the default environments - imageEnvs, err := newImage.Env(ctx) - if err != nil { - return nil, err - } - - envs, err = envLib.ParseSlice(imageEnvs) + envs, err = envLib.ParseSlice(inspectData.Config.Env) if err != nil { return nil, errors.Wrap(err, "Env fields from image failed to parse") } @@ -175,11 +125,7 @@ func CompleteSpec(ctx context.Context, r *libpod.Runtime, s *specgen.SpecGenerat } // Add annotations from the image - imgAnnotations, err := newImage.Annotations(ctx) - if err != nil { - return nil, err - } - for k, v := range imgAnnotations { + for k, v := range inspectData.Annotations { annotations[k] = v } } @@ -221,11 +167,8 @@ func CompleteSpec(ctx context.Context, r *libpod.Runtime, s *specgen.SpecGenerat s.SeccompProfilePath = p } - if len(s.User) == 0 && newImage != nil { - s.User, err = newImage.User(ctx) - if err != nil { - return nil, err - } + if len(s.User) == 0 && inspectData != nil { + s.User = inspectData.Config.User } if err := finishThrottleDevices(s); err != nil { return nil, err diff --git a/pkg/specgen/generate/container_create.go b/pkg/specgen/generate/container_create.go index 277435ef1..01f939022 100644 --- a/pkg/specgen/generate/container_create.go +++ b/pkg/specgen/generate/container_create.go @@ -7,9 +7,9 @@ import ( "strings" cdi "github.com/container-orchestrated-devices/container-device-interface/pkg" + "github.com/containers/common/libimage" "github.com/containers/common/pkg/config" "github.com/containers/podman/v3/libpod" - "github.com/containers/podman/v3/libpod/image" "github.com/containers/podman/v3/pkg/specgen" "github.com/containers/podman/v3/pkg/util" "github.com/containers/storage/types" @@ -86,11 +86,18 @@ func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGener options = append(options, libpod.WithCreateCommand(s.ContainerCreateCommand)) } - var newImage *image.Image + var newImage *libimage.Image + var imageData *libimage.ImageData if s.Rootfs != "" { options = append(options, libpod.WithRootFS(s.Rootfs)) } else { - newImage, err = rt.ImageRuntime().NewFromLocal(s.Image) + var resolvedImageName string + newImage, resolvedImageName, err = rt.LibimageRuntime().LookupImage(s.Image, nil) + if err != nil { + return nil, err + } + + imageData, err = newImage.Inspect(ctx, false) if err != nil { return nil, err } @@ -98,15 +105,14 @@ func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGener // image. Otherwise, it must have been an ID where we're // defaulting to the first name or an empty one if no names are // present. - imgName := newImage.InputName - if s.Image == newImage.InputName && strings.HasPrefix(newImage.ID(), s.Image) { + if strings.HasPrefix(newImage.ID(), resolvedImageName) { names := newImage.Names() if len(names) > 0 { - imgName = names[0] + resolvedImageName = names[0] } } - options = append(options, libpod.WithRootFSFromImage(newImage.ID(), imgName, s.RawImageName)) + options = append(options, libpod.WithRootFSFromImage(newImage.ID(), resolvedImageName, s.RawImageName)) } if err := s.Validate(); err != nil { return nil, errors.Wrap(err, "invalid config provided") @@ -117,12 +123,12 @@ func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGener return nil, err } - command, err := makeCommand(ctx, s, newImage, rtc) + command, err := makeCommand(ctx, s, imageData, rtc) if err != nil { return nil, err } - opts, err := createContainerOptions(ctx, rt, s, pod, finalVolumes, finalOverlays, newImage, command) + opts, err := createContainerOptions(ctx, rt, s, pod, finalVolumes, finalOverlays, imageData, command) if err != nil { return nil, err } @@ -176,7 +182,7 @@ func extractCDIDevices(s *specgen.SpecGenerator) []libpod.CtrCreateOption { return options } -func createContainerOptions(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGenerator, pod *libpod.Pod, volumes []*specgen.NamedVolume, overlays []*specgen.OverlayVolume, img *image.Image, command []string) ([]libpod.CtrCreateOption, error) { +func createContainerOptions(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGenerator, pod *libpod.Pod, volumes []*specgen.NamedVolume, overlays []*specgen.OverlayVolume, imageData *libimage.ImageData, command []string) ([]libpod.CtrCreateOption, error) { var options []libpod.CtrCreateOption var err error @@ -202,11 +208,8 @@ func createContainerOptions(ctx context.Context, rt *libpod.Runtime, s *specgen. case "false": break case "", "true": - if len(command) == 0 { - command, err = img.Cmd(ctx) - if err != nil { - return nil, err - } + if len(command) == 0 && imageData != nil { + command = imageData.Config.Cmd } if len(command) > 0 { @@ -308,13 +311,9 @@ func createContainerOptions(ctx context.Context, rt *libpod.Runtime, s *specgen. } // If the user did not specify a workdir on the CLI, let's extract it // from the image. - if s.WorkDir == "" && img != nil { + if s.WorkDir == "" && imageData != nil { options = append(options, libpod.WithCreateWorkingDir()) - wd, err := img.WorkingDir(ctx) - if err != nil { - return nil, err - } - s.WorkDir = wd + s.WorkDir = imageData.Config.WorkingDir } if s.WorkDir == "" { s.WorkDir = "/" @@ -367,7 +366,7 @@ func createContainerOptions(ctx context.Context, rt *libpod.Runtime, s *specgen. options = append(options, libpod.WithPrivileged(s.Privileged)) // Get namespace related options - namespaceOptions, err := namespaceOptions(ctx, s, rt, pod, img) + namespaceOptions, err := namespaceOptions(ctx, s, rt, pod, imageData) if err != nil { return nil, err } diff --git a/pkg/specgen/generate/kube/kube.go b/pkg/specgen/generate/kube/kube.go index 1347ed1e0..73c1c31ba 100644 --- a/pkg/specgen/generate/kube/kube.go +++ b/pkg/specgen/generate/kube/kube.go @@ -7,9 +7,9 @@ import ( "net" "strings" + "github.com/containers/common/libimage" "github.com/containers/common/pkg/parse" "github.com/containers/common/pkg/secrets" - "github.com/containers/podman/v3/libpod/image" ann "github.com/containers/podman/v3/pkg/annotations" "github.com/containers/podman/v3/pkg/specgen" "github.com/containers/podman/v3/pkg/util" @@ -79,7 +79,7 @@ type CtrSpecGenOptions struct { // Container as read from the pod yaml Container v1.Container // Image available to use (pulled or found local) - Image *image.Image + Image *libimage.Image // Volumes for all containers Volumes map[string]*KubeVolume // PodID of the parent pod @@ -165,7 +165,7 @@ func ToSpecGen(ctx context.Context, opts *CtrSpecGenOptions) (*specgen.SpecGener // TODO: We don't understand why specgen does not take of this, but // integration tests clearly pointed out that it was required. - imageData, err := opts.Image.Inspect(ctx) + imageData, err := opts.Image.Inspect(ctx, false) if err != nil { return nil, err } diff --git a/pkg/specgen/generate/namespaces.go b/pkg/specgen/generate/namespaces.go index b52e8d100..278f35c22 100644 --- a/pkg/specgen/generate/namespaces.go +++ b/pkg/specgen/generate/namespaces.go @@ -6,10 +6,10 @@ import ( "os" "strings" + "github.com/containers/common/libimage" "github.com/containers/common/pkg/config" "github.com/containers/podman/v3/libpod" "github.com/containers/podman/v3/libpod/define" - "github.com/containers/podman/v3/libpod/image" "github.com/containers/podman/v3/pkg/rootless" "github.com/containers/podman/v3/pkg/specgen" "github.com/containers/podman/v3/pkg/util" @@ -79,7 +79,7 @@ func GetDefaultNamespaceMode(nsType string, cfg *config.Config, pod *libpod.Pod) // joining a pod. // TODO: Consider grouping options that are not directly attached to a namespace // elsewhere. -func namespaceOptions(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runtime, pod *libpod.Pod, img *image.Image) ([]libpod.CtrCreateOption, error) { +func namespaceOptions(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runtime, pod *libpod.Pod, imageData *libimage.ImageData) ([]libpod.CtrCreateOption, error) { toReturn := []libpod.CtrCreateOption{} // If pod is not nil, get infra container. @@ -234,7 +234,7 @@ func namespaceOptions(ctx context.Context, s *specgen.SpecGenerator, rt *libpod. } toReturn = append(toReturn, libpod.WithNetNSFrom(netCtr)) case specgen.Slirp: - portMappings, err := createPortMappings(ctx, s, img) + portMappings, err := createPortMappings(ctx, s, imageData) if err != nil { return nil, err } @@ -246,7 +246,7 @@ func namespaceOptions(ctx context.Context, s *specgen.SpecGenerator, rt *libpod. case specgen.Private: fallthrough case specgen.Bridge: - portMappings, err := createPortMappings(ctx, s, img) + portMappings, err := createPortMappings(ctx, s, imageData) if err != nil { return nil, err } diff --git a/pkg/specgen/generate/oci.go b/pkg/specgen/generate/oci.go index 4eae09a5e..bf8d44ed6 100644 --- a/pkg/specgen/generate/oci.go +++ b/pkg/specgen/generate/oci.go @@ -5,10 +5,10 @@ import ( "path" "strings" + "github.com/containers/common/libimage" "github.com/containers/common/pkg/config" "github.com/containers/podman/v3/libpod" "github.com/containers/podman/v3/libpod/define" - "github.com/containers/podman/v3/libpod/image" "github.com/containers/podman/v3/pkg/cgroups" "github.com/containers/podman/v3/pkg/rootless" "github.com/containers/podman/v3/pkg/specgen" @@ -95,16 +95,12 @@ func addRlimits(s *specgen.SpecGenerator, g *generate.Generator) error { } // Produce the final command for the container. -func makeCommand(ctx context.Context, s *specgen.SpecGenerator, img *image.Image, rtc *config.Config) ([]string, error) { +func makeCommand(ctx context.Context, s *specgen.SpecGenerator, imageData *libimage.ImageData, rtc *config.Config) ([]string, error) { finalCommand := []string{} entrypoint := s.Entrypoint - if entrypoint == nil && img != nil { - newEntry, err := img.Entrypoint(ctx) - if err != nil { - return nil, err - } - entrypoint = newEntry + if entrypoint == nil && imageData != nil { + entrypoint = imageData.Config.Entrypoint } // Don't append the entrypoint if it is [""] @@ -115,12 +111,8 @@ func makeCommand(ctx context.Context, s *specgen.SpecGenerator, img *image.Image // Only use image command if the user did not manually set an // entrypoint. command := s.Command - if len(command) == 0 && img != nil && len(s.Entrypoint) == 0 { - newCmd, err := img.Cmd(ctx) - if err != nil { - return nil, err - } - command = newCmd + if len(command) == 0 && imageData != nil && len(s.Entrypoint) == 0 { + command = imageData.Config.Cmd } finalCommand = append(finalCommand, command...) @@ -182,7 +174,7 @@ func getCGroupPermissons(unmask []string) string { } // SpecGenToOCI returns the base configuration for the container. -func SpecGenToOCI(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runtime, rtc *config.Config, newImage *image.Image, mounts []spec.Mount, pod *libpod.Pod, finalCmd []string) (*spec.Spec, error) { +func SpecGenToOCI(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runtime, rtc *config.Config, newImage *libimage.Image, mounts []spec.Mount, pod *libpod.Pod, finalCmd []string) (*spec.Spec, error) { cgroupPerm := getCGroupPermissons(s.Unmask) g, err := generate.New("linux") diff --git a/pkg/specgen/generate/ports.go b/pkg/specgen/generate/ports.go index 678e36a70..6832664a7 100644 --- a/pkg/specgen/generate/ports.go +++ b/pkg/specgen/generate/ports.go @@ -6,9 +6,9 @@ import ( "strconv" "strings" + "github.com/containers/common/libimage" "github.com/containers/podman/v3/utils" - "github.com/containers/podman/v3/libpod/image" "github.com/containers/podman/v3/pkg/specgen" "github.com/cri-o/ocicni/pkg/ocicni" "github.com/pkg/errors" @@ -253,7 +253,7 @@ func parsePortMapping(portMappings []specgen.PortMapping) ([]ocicni.PortMapping, } // Make final port mappings for the container -func createPortMappings(ctx context.Context, s *specgen.SpecGenerator, img *image.Image) ([]ocicni.PortMapping, error) { +func createPortMappings(ctx context.Context, s *specgen.SpecGenerator, imageData *libimage.ImageData) ([]ocicni.PortMapping, error) { finalMappings, containerPortValidate, hostPortValidate, err := parsePortMapping(s.PortMappings) if err != nil { return nil, err @@ -262,7 +262,7 @@ func createPortMappings(ctx context.Context, s *specgen.SpecGenerator, img *imag // If not publishing exposed ports, or if we are publishing and there is // nothing to publish - then just return the port mappings we've made so // far. - if !s.PublishExposedPorts || (len(s.Expose) == 0 && img == nil) { + if !s.PublishExposedPorts || (len(s.Expose) == 0 && imageData == nil) { return finalMappings, nil } @@ -273,12 +273,8 @@ func createPortMappings(ctx context.Context, s *specgen.SpecGenerator, img *imag for k, v := range s.Expose { expose[k] = v } - if img != nil { - inspect, err := img.InspectNoSize(ctx) - if err != nil { - return nil, errors.Wrapf(err, "error inspecting image to get exposed ports") - } - for imgExpose := range inspect.Config.ExposedPorts { + if imageData != nil { + for imgExpose := range imageData.Config.ExposedPorts { // Expose format is portNumber[/protocol] splitExpose := strings.SplitN(imgExpose, "/", 2) num, err := strconv.Atoi(splitExpose[0]) diff --git a/pkg/specgen/generate/security.go b/pkg/specgen/generate/security.go index e0e4a47a4..a12cc09e2 100644 --- a/pkg/specgen/generate/security.go +++ b/pkg/specgen/generate/security.go @@ -3,12 +3,12 @@ package generate import ( "strings" + "github.com/containers/common/libimage" "github.com/containers/common/pkg/apparmor" "github.com/containers/common/pkg/capabilities" "github.com/containers/common/pkg/config" "github.com/containers/podman/v3/libpod" "github.com/containers/podman/v3/libpod/define" - "github.com/containers/podman/v3/libpod/image" "github.com/containers/podman/v3/pkg/specgen" "github.com/containers/podman/v3/pkg/util" "github.com/opencontainers/runtime-tools/generate" @@ -80,7 +80,7 @@ func setupApparmor(s *specgen.SpecGenerator, rtc *config.Config, g *generate.Gen return nil } -func securityConfigureGenerator(s *specgen.SpecGenerator, g *generate.Generator, newImage *image.Image, rtc *config.Config) error { +func securityConfigureGenerator(s *specgen.SpecGenerator, g *generate.Generator, newImage *libimage.Image, rtc *config.Config) error { var ( caplist []string err error diff --git a/pkg/specgen/generate/storage.go b/pkg/specgen/generate/storage.go index 8066834f7..13f336594 100644 --- a/pkg/specgen/generate/storage.go +++ b/pkg/specgen/generate/storage.go @@ -8,10 +8,10 @@ import ( "path/filepath" "strings" + "github.com/containers/common/libimage" "github.com/containers/common/pkg/config" "github.com/containers/podman/v3/libpod" "github.com/containers/podman/v3/libpod/define" - "github.com/containers/podman/v3/libpod/image" "github.com/containers/podman/v3/pkg/specgen" "github.com/containers/podman/v3/pkg/util" spec "github.com/opencontainers/runtime-spec/specs-go" @@ -24,7 +24,7 @@ var ( ) // Produce final mounts and named volumes for a container -func finalizeMounts(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runtime, rtc *config.Config, img *image.Image) ([]spec.Mount, []*specgen.NamedVolume, []*specgen.OverlayVolume, error) { +func finalizeMounts(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runtime, rtc *config.Config, img *libimage.Image) ([]spec.Mount, []*specgen.NamedVolume, []*specgen.OverlayVolume, error) { // Get image volumes baseMounts, baseVolumes, err := getImageVolumes(ctx, img, s) if err != nil { @@ -173,7 +173,7 @@ func finalizeMounts(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Ru } // Get image volumes from the given image -func getImageVolumes(ctx context.Context, img *image.Image, s *specgen.SpecGenerator) (map[string]spec.Mount, map[string]*specgen.NamedVolume, error) { +func getImageVolumes(ctx context.Context, img *libimage.Image, s *specgen.SpecGenerator) (map[string]spec.Mount, map[string]*specgen.NamedVolume, error) { mounts := make(map[string]spec.Mount) volumes := make(map[string]*specgen.NamedVolume) @@ -184,7 +184,7 @@ func getImageVolumes(ctx context.Context, img *image.Image, s *specgen.SpecGener return mounts, volumes, nil } - inspect, err := img.InspectNoSize(ctx) + inspect, err := img.Inspect(ctx, false) if err != nil { return nil, nil, errors.Wrapf(err, "error inspecting image to get image volumes") } diff --git a/pkg/util/utils.go b/pkg/util/utils.go index 622fbde99..60aa64ac1 100644 --- a/pkg/util/utils.go +++ b/pkg/util/utils.go @@ -554,23 +554,6 @@ func OpenExclusiveFile(path string) (*os.File, error) { return os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666) } -type PullType = config.PullPolicy - -var ( - // PullImageAlways always try to pull new image when create or run - PullImageAlways = config.PullImageAlways - // PullImageMissing pulls image if it is not locally - PullImageMissing = config.PullImageMissing - // PullImageNever will never pull new image - PullImageNever = config.PullImageNever -) - -// ValidatePullType check if the pullType from CLI is valid and returns the valid enum type -// if the value from CLI is invalid returns the error -func ValidatePullType(pullType string) (PullType, error) { - return config.ValidatePullPolicy(pullType) -} - // ExitCode reads the error message when failing to executing container process // and then returns 0 if no error, 126 if command does not exist, or 127 for // all other errors |