mirror of
https://github.com/containers/podman.git
synced 2025-12-15 19:49:29 +08:00
With the advent of Podman 2.0.0 we crossed the magical barrier of go modules. While we were able to continue importing all packages inside of the project, the project could not be vendored anymore from the outside. Move the go module to new major version and change all imports to `github.com/containers/libpod/v2`. The renaming of the imports was done via `gomove` [1]. [1] https://github.com/KSubedi/gomove Signed-off-by: Valentin Rothberg <rothberg@redhat.com>
757 lines
25 KiB
Go
757 lines
25 KiB
Go
package libpod
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/containers/buildah"
|
|
"github.com/containers/image/v5/docker"
|
|
"github.com/containers/image/v5/docker/reference"
|
|
"github.com/containers/image/v5/manifest"
|
|
"github.com/containers/image/v5/types"
|
|
"github.com/containers/libpod/v2/libpod"
|
|
"github.com/containers/libpod/v2/libpod/define"
|
|
"github.com/containers/libpod/v2/libpod/image"
|
|
image2 "github.com/containers/libpod/v2/libpod/image"
|
|
"github.com/containers/libpod/v2/pkg/api/handlers"
|
|
"github.com/containers/libpod/v2/pkg/api/handlers/utils"
|
|
"github.com/containers/libpod/v2/pkg/auth"
|
|
"github.com/containers/libpod/v2/pkg/domain/entities"
|
|
"github.com/containers/libpod/v2/pkg/domain/infra/abi"
|
|
"github.com/containers/libpod/v2/pkg/errorhandling"
|
|
"github.com/containers/libpod/v2/pkg/util"
|
|
utils2 "github.com/containers/libpod/v2/utils"
|
|
"github.com/gorilla/schema"
|
|
"github.com/pkg/errors"
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
// Commit
|
|
// author string
|
|
// "container"
|
|
// repo string
|
|
// tag string
|
|
// message
|
|
// pause bool
|
|
// changes []string
|
|
|
|
// create
|
|
|
|
func ImageExists(w http.ResponseWriter, r *http.Request) {
|
|
runtime := r.Context().Value("runtime").(*libpod.Runtime)
|
|
name := utils.GetName(r)
|
|
|
|
_, err := runtime.ImageRuntime().NewFromLocal(name)
|
|
if err != nil {
|
|
utils.Error(w, "Something went wrong.", http.StatusNotFound, errors.Wrapf(err, "Failed to find image %s", name))
|
|
return
|
|
}
|
|
utils.WriteResponse(w, http.StatusNoContent, "")
|
|
}
|
|
|
|
func ImageTree(w http.ResponseWriter, r *http.Request) {
|
|
runtime := r.Context().Value("runtime").(*libpod.Runtime)
|
|
name := utils.GetName(r)
|
|
decoder := r.Context().Value("decoder").(*schema.Decoder)
|
|
query := struct {
|
|
WhatRequires bool `schema:"whatrequires"`
|
|
}{
|
|
WhatRequires: false,
|
|
}
|
|
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
|
|
}
|
|
ir := abi.ImageEngine{Libpod: runtime}
|
|
options := entities.ImageTreeOptions{WhatRequires: query.WhatRequires}
|
|
report, err := ir.Tree(r.Context(), name, options)
|
|
if err != nil {
|
|
if errors.Cause(err) == define.ErrNoSuchImage {
|
|
utils.Error(w, "Something went wrong.", http.StatusNotFound, errors.Wrapf(err, "Failed to find image %s", name))
|
|
return
|
|
}
|
|
utils.Error(w, "Server error", http.StatusInternalServerError, errors.Wrapf(err, "failed to generate image tree for %s", name))
|
|
return
|
|
}
|
|
utils.WriteResponse(w, http.StatusOK, report)
|
|
}
|
|
|
|
func GetImage(w http.ResponseWriter, r *http.Request) {
|
|
name := utils.GetName(r)
|
|
newImage, err := utils.GetImage(r, name)
|
|
if err != nil {
|
|
utils.Error(w, "Something went wrong.", http.StatusNotFound, errors.Wrapf(err, "Failed to find image %s", name))
|
|
return
|
|
}
|
|
inspect, err := newImage.Inspect(r.Context())
|
|
if err != nil {
|
|
utils.Error(w, "Server error", http.StatusInternalServerError, errors.Wrapf(err, "failed in inspect image %s", inspect.ID))
|
|
return
|
|
}
|
|
utils.WriteResponse(w, http.StatusOK, inspect)
|
|
}
|
|
|
|
func GetImages(w http.ResponseWriter, r *http.Request) {
|
|
images, err := utils.GetImages(w, r)
|
|
if err != nil {
|
|
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Failed get images"))
|
|
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
|
|
}
|
|
utils.WriteResponse(w, http.StatusOK, summaries)
|
|
}
|
|
|
|
func PruneImages(w http.ResponseWriter, r *http.Request) {
|
|
var (
|
|
err error
|
|
)
|
|
runtime := r.Context().Value("runtime").(*libpod.Runtime)
|
|
decoder := r.Context().Value("decoder").(*schema.Decoder)
|
|
query := struct {
|
|
All bool `schema:"all"`
|
|
Filters map[string][]string `schema:"filters"`
|
|
}{
|
|
// override any golang type defaults
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
var libpodFilters = []string{}
|
|
if _, found := r.URL.Query()["filters"]; found {
|
|
dangling := query.Filters["all"]
|
|
if len(dangling) > 0 {
|
|
query.All, err = strconv.ParseBool(query.Filters["all"][0])
|
|
if err != nil {
|
|
utils.InternalServerError(w, err)
|
|
return
|
|
}
|
|
}
|
|
// dangling is special and not implemented in the libpod side of things
|
|
delete(query.Filters, "dangling")
|
|
for k, v := range query.Filters {
|
|
libpodFilters = append(libpodFilters, fmt.Sprintf("%s=%s", k, v[0]))
|
|
}
|
|
}
|
|
|
|
cids, err := runtime.ImageRuntime().PruneImages(r.Context(), query.All, libpodFilters)
|
|
if err != nil {
|
|
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
utils.WriteResponse(w, http.StatusOK, cids)
|
|
}
|
|
|
|
func ExportImage(w http.ResponseWriter, r *http.Request) {
|
|
var (
|
|
output string
|
|
)
|
|
runtime := r.Context().Value("runtime").(*libpod.Runtime)
|
|
decoder := r.Context().Value("decoder").(*schema.Decoder)
|
|
query := struct {
|
|
Compress bool `schema:"compress"`
|
|
Format string `schema:"format"`
|
|
}{
|
|
Format: define.OCIArchive,
|
|
}
|
|
|
|
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
|
|
}
|
|
name := utils.GetName(r)
|
|
newImage, err := runtime.ImageRuntime().NewFromLocal(name)
|
|
if err != nil {
|
|
utils.ImageNotFound(w, name, err)
|
|
return
|
|
}
|
|
switch query.Format {
|
|
case define.OCIArchive, define.V2s2Archive:
|
|
tmpfile, err := ioutil.TempFile("", "api.tar")
|
|
if err != nil {
|
|
utils.Error(w, "unable to create tmpfile", http.StatusInternalServerError, errors.Wrap(err, "unable to create tempfile"))
|
|
return
|
|
}
|
|
output = tmpfile.Name()
|
|
if err := tmpfile.Close(); err != nil {
|
|
utils.Error(w, "unable to close tmpfile", http.StatusInternalServerError, errors.Wrap(err, "unable to close tempfile"))
|
|
return
|
|
}
|
|
case define.OCIManifestDir, define.V2s2ManifestDir:
|
|
tmpdir, err := ioutil.TempDir("", "save")
|
|
if err != nil {
|
|
utils.Error(w, "unable to create tmpdir", http.StatusInternalServerError, errors.Wrap(err, "unable to create tempdir"))
|
|
return
|
|
}
|
|
output = tmpdir
|
|
default:
|
|
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); err != nil {
|
|
utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, err)
|
|
return
|
|
}
|
|
defer os.RemoveAll(output)
|
|
// if dir format, we need to tar it
|
|
if query.Format == "oci-dir" || query.Format == "docker-dir" {
|
|
rdr, err := utils2.Tar(output)
|
|
if err != nil {
|
|
utils.InternalServerError(w, err)
|
|
return
|
|
}
|
|
defer rdr.Close()
|
|
utils.WriteResponse(w, http.StatusOK, rdr)
|
|
return
|
|
}
|
|
rdr, err := os.Open(output)
|
|
if err != nil {
|
|
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to read the exported tarfile"))
|
|
return
|
|
}
|
|
defer rdr.Close()
|
|
utils.WriteResponse(w, http.StatusOK, rdr)
|
|
}
|
|
|
|
func ImagesLoad(w http.ResponseWriter, r *http.Request) {
|
|
runtime := r.Context().Value("runtime").(*libpod.Runtime)
|
|
decoder := r.Context().Value("decoder").(*schema.Decoder)
|
|
query := struct {
|
|
Reference string `schema:"reference"`
|
|
}{
|
|
// Add defaults here once needed.
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
tmpfile, err := ioutil.TempFile("", "libpod-images-load.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())
|
|
defer tmpfile.Close()
|
|
|
|
if _, err := io.Copy(tmpfile, r.Body); err != nil && err != io.EOF {
|
|
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to write archive to temporary file"))
|
|
return
|
|
}
|
|
|
|
tmpfile.Close()
|
|
loadedImage, err := runtime.LoadImage(context.Background(), query.Reference, tmpfile.Name(), os.Stderr, "")
|
|
if err != nil {
|
|
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to load image"))
|
|
return
|
|
}
|
|
split := strings.Split(loadedImage, ",")
|
|
newImage, err := runtime.ImageRuntime().NewFromLocal(split[0])
|
|
if err != nil {
|
|
utils.InternalServerError(w, err)
|
|
return
|
|
}
|
|
// TODO this should go into libpod proper at some point.
|
|
if len(query.Reference) > 0 {
|
|
if err := newImage.TagImage(query.Reference); err != nil {
|
|
utils.InternalServerError(w, err)
|
|
return
|
|
}
|
|
}
|
|
utils.WriteResponse(w, http.StatusOK, entities.ImageLoadReport{Names: split})
|
|
}
|
|
|
|
func ImagesImport(w http.ResponseWriter, r *http.Request) {
|
|
runtime := r.Context().Value("runtime").(*libpod.Runtime)
|
|
decoder := r.Context().Value("decoder").(*schema.Decoder)
|
|
query := struct {
|
|
Changes []string `schema:"changes"`
|
|
Message string `schema:"message"`
|
|
Reference string `schema:"reference"`
|
|
URL string `schema:"URL"`
|
|
}{
|
|
// Add defaults here once needed.
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
// Check if we need to load the image from a URL or from the request's body.
|
|
source := query.URL
|
|
if len(query.URL) == 0 {
|
|
tmpfile, err := ioutil.TempFile("", "libpod-images-import.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())
|
|
defer tmpfile.Close()
|
|
|
|
if _, err := io.Copy(tmpfile, r.Body); err != nil && err != io.EOF {
|
|
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to write archive to temporary file"))
|
|
return
|
|
}
|
|
|
|
tmpfile.Close()
|
|
source = tmpfile.Name()
|
|
}
|
|
importedImage, err := runtime.Import(context.Background(), source, query.Reference, query.Changes, query.Message, true)
|
|
if err != nil {
|
|
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to import image"))
|
|
return
|
|
}
|
|
|
|
utils.WriteResponse(w, http.StatusOK, entities.ImageImportReport{Id: importedImage})
|
|
}
|
|
|
|
// ImagesPull is the v2 libpod endpoint for pulling images. Note that the
|
|
// mandatory `reference` must be a reference to a registry (i.e., of docker
|
|
// transport or be normalized to one). Other transports are rejected as they
|
|
// do not make sense in a remote context.
|
|
func ImagesPull(w http.ResponseWriter, r *http.Request) {
|
|
runtime := r.Context().Value("runtime").(*libpod.Runtime)
|
|
decoder := r.Context().Value("decoder").(*schema.Decoder)
|
|
query := struct {
|
|
Reference string `schema:"reference"`
|
|
OverrideOS string `schema:"overrideOS"`
|
|
OverrideArch string `schema:"overrideArch"`
|
|
TLSVerify bool `schema:"tlsVerify"`
|
|
AllTags bool `schema:"allTags"`
|
|
}{
|
|
TLSVerify: true,
|
|
}
|
|
|
|
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 len(query.Reference) == 0 {
|
|
utils.InternalServerError(w, errors.New("reference parameter cannot be empty"))
|
|
return
|
|
}
|
|
|
|
imageRef, err := utils.ParseDockerReference(query.Reference)
|
|
if err != nil {
|
|
utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
|
|
errors.Wrapf(err, "image destination %q is not a docker-transport reference", query.Reference))
|
|
return
|
|
}
|
|
|
|
// Trim the docker-transport prefix.
|
|
rawImage := strings.TrimPrefix(query.Reference, fmt.Sprintf("%s://", docker.Transport.Name()))
|
|
|
|
// 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
|
|
}
|
|
|
|
authConf, authfile, err := auth.GetCredentials(r)
|
|
if err != nil {
|
|
utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse %q header for %s", auth.XRegistryAuthHeader, r.URL.String()))
|
|
return
|
|
}
|
|
defer auth.RemoveAuthfile(authfile)
|
|
|
|
// Setup the registry options
|
|
dockerRegistryOptions := image.DockerRegistryOptions{
|
|
DockerRegistryCreds: authConf,
|
|
OSChoice: query.OverrideOS,
|
|
ArchitectureChoice: query.OverrideArch,
|
|
}
|
|
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{}
|
|
res := []handlers.LibpodImagesPullReport{}
|
|
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))
|
|
}
|
|
}
|
|
|
|
// Finally pull the images
|
|
for _, img := range imagesToPull {
|
|
newImage, err := runtime.ImageRuntime().New(
|
|
context.Background(),
|
|
img,
|
|
"",
|
|
authfile,
|
|
os.Stderr,
|
|
&dockerRegistryOptions,
|
|
image.SigningOptions{},
|
|
nil,
|
|
util.PullImageAlways)
|
|
if err != nil {
|
|
utils.InternalServerError(w, err)
|
|
return
|
|
}
|
|
res = append(res, handlers.LibpodImagesPullReport{ID: newImage.ID()})
|
|
}
|
|
|
|
utils.WriteResponse(w, http.StatusOK, res)
|
|
}
|
|
|
|
// PushImage is the handler for the compat http endpoint for pushing images.
|
|
func PushImage(w http.ResponseWriter, r *http.Request) {
|
|
decoder := r.Context().Value("decoder").(*schema.Decoder)
|
|
runtime := r.Context().Value("runtime").(*libpod.Runtime)
|
|
|
|
query := struct {
|
|
Destination string `schema:"destination"`
|
|
TLSVerify bool `schema:"tlsVerify"`
|
|
}{
|
|
// This is where you can override the golang default value for one of fields
|
|
}
|
|
|
|
if err := decoder.Decode(&query, r.URL.Query()); err != nil {
|
|
utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
|
|
return
|
|
}
|
|
|
|
source := strings.TrimSuffix(utils.GetName(r), "/push") // GetName returns the entire path
|
|
if _, err := utils.ParseStorageReference(source); err != nil {
|
|
utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
|
|
errors.Wrapf(err, "image source %q is not a containers-storage-transport reference", source))
|
|
return
|
|
}
|
|
|
|
destination := query.Destination
|
|
if destination == "" {
|
|
destination = source
|
|
}
|
|
|
|
if _, err := utils.ParseDockerReference(destination); err != nil {
|
|
utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
|
|
errors.Wrapf(err, "image destination %q is not a docker-transport reference", destination))
|
|
return
|
|
}
|
|
|
|
newImage, err := runtime.ImageRuntime().NewFromLocal(source)
|
|
if err != nil {
|
|
utils.ImageNotFound(w, source, errors.Wrapf(err, "Failed to find image %s", source))
|
|
return
|
|
}
|
|
|
|
authConf, authfile, err := auth.GetCredentials(r)
|
|
if err != nil {
|
|
utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse %q header for %s", auth.XRegistryAuthHeader, r.URL.String()))
|
|
return
|
|
}
|
|
defer auth.RemoveAuthfile(authfile)
|
|
logrus.Errorf("AuthConf: %v", authConf)
|
|
|
|
dockerRegistryOptions := &image.DockerRegistryOptions{
|
|
DockerRegistryCreds: authConf,
|
|
}
|
|
if sys := runtime.SystemContext(); sys != nil {
|
|
dockerRegistryOptions.DockerCertPath = sys.DockerCertPath
|
|
dockerRegistryOptions.RegistriesConfPath = sys.SystemRegistriesConfPath
|
|
}
|
|
if _, found := r.URL.Query()["tlsVerify"]; found {
|
|
dockerRegistryOptions.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!query.TLSVerify)
|
|
}
|
|
|
|
err = newImage.PushImageToHeuristicDestination(
|
|
context.Background(),
|
|
destination,
|
|
"", // manifest type
|
|
authfile,
|
|
"", // digest file
|
|
"", // signature policy
|
|
os.Stderr,
|
|
false, // force compression
|
|
image.SigningOptions{},
|
|
dockerRegistryOptions,
|
|
nil, // additional tags
|
|
)
|
|
if err != nil {
|
|
utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Error pushing image %q", destination))
|
|
return
|
|
}
|
|
|
|
utils.WriteResponse(w, http.StatusOK, "")
|
|
}
|
|
|
|
func CommitContainer(w http.ResponseWriter, r *http.Request) {
|
|
var (
|
|
destImage string
|
|
mimeType string
|
|
)
|
|
decoder := r.Context().Value("decoder").(*schema.Decoder)
|
|
runtime := r.Context().Value("runtime").(*libpod.Runtime)
|
|
|
|
query := struct {
|
|
Author string `schema:"author"`
|
|
Changes []string `schema:"changes"`
|
|
Comment string `schema:"comment"`
|
|
Container string `schema:"container"`
|
|
Format string `schema:"format"`
|
|
Pause bool `schema:"pause"`
|
|
Repo string `schema:"repo"`
|
|
Tag string `schema:"tag"`
|
|
}{
|
|
Format: "oci",
|
|
}
|
|
|
|
if err := decoder.Decode(&query, r.URL.Query()); err != nil {
|
|
utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
|
|
return
|
|
}
|
|
rtc, err := runtime.GetConfig()
|
|
if err != nil {
|
|
utils.Error(w, "failed to get runtime config", http.StatusInternalServerError, errors.Wrap(err, "failed to get runtime config"))
|
|
return
|
|
}
|
|
sc := image2.GetSystemContext(rtc.Engine.SignaturePolicyPath, "", false)
|
|
tag := "latest"
|
|
options := libpod.ContainerCommitOptions{
|
|
Pause: true,
|
|
}
|
|
switch query.Format {
|
|
case "oci":
|
|
mimeType = buildah.OCIv1ImageManifest
|
|
if len(query.Comment) > 0 {
|
|
utils.InternalServerError(w, errors.New("messages are only compatible with the docker image format (-f docker)"))
|
|
return
|
|
}
|
|
case "docker":
|
|
mimeType = manifest.DockerV2Schema2MediaType
|
|
default:
|
|
utils.InternalServerError(w, errors.Errorf("unrecognized image format %q", query.Format))
|
|
return
|
|
}
|
|
options.CommitOptions = buildah.CommitOptions{
|
|
SignaturePolicyPath: rtc.Engine.SignaturePolicyPath,
|
|
ReportWriter: os.Stderr,
|
|
SystemContext: sc,
|
|
PreferredManifestType: mimeType,
|
|
}
|
|
|
|
if len(query.Tag) > 0 {
|
|
tag = query.Tag
|
|
}
|
|
options.Message = query.Comment
|
|
options.Author = query.Author
|
|
options.Pause = query.Pause
|
|
options.Changes = query.Changes
|
|
ctr, err := runtime.LookupContainer(query.Container)
|
|
if err != nil {
|
|
utils.Error(w, "failed to lookup container", http.StatusNotFound, err)
|
|
return
|
|
}
|
|
|
|
// I know mitr hates this ... but doing for now
|
|
if len(query.Repo) > 1 {
|
|
destImage = fmt.Sprintf("%s:%s", query.Repo, tag)
|
|
}
|
|
|
|
commitImage, err := ctr.Commit(r.Context(), destImage, options)
|
|
if err != nil && !strings.Contains(err.Error(), "is not running") {
|
|
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrapf(err, "CommitFailure"))
|
|
return
|
|
}
|
|
utils.WriteResponse(w, http.StatusOK, handlers.IDResponse{ID: commitImage.ID()}) // nolint
|
|
}
|
|
|
|
func UntagImage(w http.ResponseWriter, r *http.Request) {
|
|
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
|
|
}
|
|
tag := "latest"
|
|
if len(r.Form.Get("tag")) > 0 {
|
|
tag = r.Form.Get("tag")
|
|
}
|
|
if len(r.Form.Get("repo")) < 1 {
|
|
utils.Error(w, "repo tag is required", http.StatusBadRequest, errors.New("repo parameter is required to tag an image"))
|
|
return
|
|
}
|
|
repo := r.Form.Get("repo")
|
|
tagName := fmt.Sprintf("%s:%s", repo, tag)
|
|
if err := newImage.UntagImage(tagName); err != nil {
|
|
utils.Error(w, "failed to untag", http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
utils.WriteResponse(w, http.StatusCreated, "")
|
|
}
|
|
|
|
func SearchImages(w http.ResponseWriter, r *http.Request) {
|
|
decoder := r.Context().Value("decoder").(*schema.Decoder)
|
|
query := struct {
|
|
Term string `json:"term"`
|
|
Limit int `json:"limit"`
|
|
Filters []string `json:"filters"`
|
|
TLSVerify bool `json:"tlsVerify"`
|
|
}{
|
|
// This is where you can override the golang default value for one of fields
|
|
}
|
|
|
|
if err := decoder.Decode(&query, r.URL.Query()); err != nil {
|
|
utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
|
|
return
|
|
}
|
|
|
|
options := image.SearchOptions{
|
|
Limit: query.Limit,
|
|
}
|
|
if _, found := r.URL.Query()["tlsVerify"]; found {
|
|
options.InsecureSkipTLSVerify = types.NewOptionalBool(!query.TLSVerify)
|
|
}
|
|
|
|
if _, found := r.URL.Query()["filters"]; found {
|
|
filter, err := image.ParseSearchFilter(query.Filters)
|
|
if err != nil {
|
|
utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse filters parameter for %s", r.URL.String()))
|
|
return
|
|
}
|
|
options.Filter = *filter
|
|
}
|
|
|
|
searchResults, err := image.SearchImages(query.Term, options)
|
|
if err != nil {
|
|
utils.BadRequest(w, "term", query.Term, err)
|
|
return
|
|
}
|
|
// Convert from image.SearchResults to entities.ImageSearchReport. We don't
|
|
// want to leak any low-level packages into the remote client, which
|
|
// requires converting.
|
|
reports := make([]entities.ImageSearchReport, len(searchResults))
|
|
for i := range searchResults {
|
|
reports[i].Index = searchResults[i].Index
|
|
reports[i].Name = searchResults[i].Name
|
|
reports[i].Description = searchResults[i].Index
|
|
reports[i].Stars = searchResults[i].Stars
|
|
reports[i].Official = searchResults[i].Official
|
|
reports[i].Automated = searchResults[i].Automated
|
|
}
|
|
|
|
utils.WriteResponse(w, http.StatusOK, reports)
|
|
}
|
|
|
|
// ImagesBatchRemove is the endpoint for batch image removal.
|
|
func ImagesBatchRemove(w http.ResponseWriter, r *http.Request) {
|
|
runtime := r.Context().Value("runtime").(*libpod.Runtime)
|
|
decoder := r.Context().Value("decoder").(*schema.Decoder)
|
|
query := struct {
|
|
All bool `schema:"all"`
|
|
Force bool `schema:"force"`
|
|
Images []string `schema:"images"`
|
|
}{
|
|
All: false,
|
|
Force: false,
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
opts := entities.ImageRemoveOptions{All: query.All, Force: query.Force}
|
|
|
|
imageEngine := abi.ImageEngine{Libpod: runtime}
|
|
rmReport, rmErrors := imageEngine.Remove(r.Context(), query.Images, opts)
|
|
|
|
strErrs := errorhandling.ErrorsToStrings(rmErrors)
|
|
report := handlers.LibpodImagesRemoveReport{ImageRemoveReport: *rmReport, Errors: strErrs}
|
|
utils.WriteResponse(w, http.StatusOK, report)
|
|
}
|
|
|
|
// ImagesRemove is the endpoint for removing one image.
|
|
func ImagesRemove(w http.ResponseWriter, r *http.Request) {
|
|
runtime := r.Context().Value("runtime").(*libpod.Runtime)
|
|
decoder := r.Context().Value("decoder").(*schema.Decoder)
|
|
query := struct {
|
|
Force bool `schema:"force"`
|
|
}{
|
|
Force: false,
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
opts := entities.ImageRemoveOptions{Force: query.Force}
|
|
imageEngine := abi.ImageEngine{Libpod: runtime}
|
|
rmReport, rmErrors := imageEngine.Remove(r.Context(), []string{utils.GetName(r)}, opts)
|
|
|
|
// In contrast to batch-removal, where we're only setting the exit
|
|
// code, we need to have another closer look at the errors here and set
|
|
// the appropriate http status code.
|
|
|
|
switch rmReport.ExitCode {
|
|
case 0:
|
|
report := handlers.LibpodImagesRemoveReport{ImageRemoveReport: *rmReport, Errors: []string{}}
|
|
utils.WriteResponse(w, http.StatusOK, report)
|
|
case 1:
|
|
// 404 - no such image
|
|
utils.Error(w, "error removing image", http.StatusNotFound, errorhandling.JoinErrors(rmErrors))
|
|
case 2:
|
|
// 409 - conflict error (in use by containers)
|
|
utils.Error(w, "error removing image", http.StatusConflict, errorhandling.JoinErrors(rmErrors))
|
|
default:
|
|
// 500 - internal error
|
|
utils.Error(w, "failed to remove image", http.StatusInternalServerError, errorhandling.JoinErrors(rmErrors))
|
|
}
|
|
}
|