mirror of
				https://github.com/containers/podman.git
				synced 2025-10-31 10:00:01 +08:00 
			
		
		
		
	 86335aa4ae
			
		
	
	86335aa4ae
	
	
	
		
			
			manifest, system, info, volumes, play, and generate bindings are updated to always have binding options. Signed-off-by: baude <bbaude@redhat.com>
		
			
				
	
	
		
			768 lines
		
	
	
		
			25 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			768 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/manifest"
 | |
| 	"github.com/containers/image/v5/types"
 | |
| 	"github.com/containers/podman/v2/libpod"
 | |
| 	"github.com/containers/podman/v2/libpod/define"
 | |
| 	"github.com/containers/podman/v2/libpod/image"
 | |
| 	image2 "github.com/containers/podman/v2/libpod/image"
 | |
| 	"github.com/containers/podman/v2/pkg/api/handlers"
 | |
| 	"github.com/containers/podman/v2/pkg/api/handlers/utils"
 | |
| 	"github.com/containers/podman/v2/pkg/auth"
 | |
| 	"github.com/containers/podman/v2/pkg/domain/entities"
 | |
| 	"github.com/containers/podman/v2/pkg/domain/infra/abi"
 | |
| 	"github.com/containers/podman/v2/pkg/errorhandling"
 | |
| 	utils2 "github.com/containers/podman/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)
 | |
| 
 | |
| 	ir := abi.ImageEngine{Libpod: runtime}
 | |
| 	report, err := ir.Exists(r.Context(), name)
 | |
| 	if err != nil {
 | |
| 		utils.Error(w, "Something went wrong.", http.StatusNotFound, errors.Wrapf(err, "failed to find image %s", name))
 | |
| 		return
 | |
| 	}
 | |
| 	if !report.Value {
 | |
| 		utils.Error(w, "Something went wrong.", http.StatusNotFound, errors.Errorf("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, true); 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 ExportImages(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"`
 | |
| 		References []string `schema:"references"`
 | |
| 	}{
 | |
| 		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
 | |
| 	}
 | |
| 
 | |
| 	// References are mandatory!
 | |
| 	if len(query.References) == 0 {
 | |
| 		utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
 | |
| 			errors.New("No references"))
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	// Format is mandatory! Currently, we only support multi-image docker
 | |
| 	// archives.
 | |
| 	if len(query.References) > 1 && query.Format != define.V2s2Archive {
 | |
| 		utils.Error(w, "unsupported format", http.StatusInternalServerError, errors.Errorf("multi-image archives must use format of %s", define.V2s2Archive))
 | |
| 		return
 | |
| 
 | |
| 	}
 | |
| 
 | |
| 	switch query.Format {
 | |
| 	case define.V2s2Archive, define.OCIArchive:
 | |
| 		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 tmpdir"))
 | |
| 			return
 | |
| 		}
 | |
| 		output = tmpdir
 | |
| 	default:
 | |
| 		utils.Error(w, "unsupported format", http.StatusInternalServerError, errors.Errorf("unsupported format %q", query.Format))
 | |
| 		return
 | |
| 	}
 | |
| 	defer os.RemoveAll(output)
 | |
| 
 | |
| 	// Use the ABI image engine to share as much code as possible.
 | |
| 	opts := entities.ImageSaveOptions{
 | |
| 		Compress:          query.Compress,
 | |
| 		Format:            query.Format,
 | |
| 		MultiImageArchive: len(query.References) > 1,
 | |
| 		Output:            output,
 | |
| 		RemoveSignatures:  true,
 | |
| 	}
 | |
| 
 | |
| 	imageEngine := abi.ImageEngine{Libpod: runtime}
 | |
| 	if err := imageEngine.Save(r.Context(), query.References[0], query.References[1:], opts); err != nil {
 | |
| 		utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, err)
 | |
| 		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(), 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})
 | |
| }
 | |
| 
 | |
| // 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, err)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	destination := query.Destination
 | |
| 	if destination == "" {
 | |
| 		destination = source
 | |
| 	}
 | |
| 
 | |
| 	if _, err := utils.ParseDockerReference(destination); err != nil {
 | |
| 		utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, err)
 | |
| 		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, 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()))
 | |
| 		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
 | |
| 	}
 | |
| 
 | |
| 	if len(query.Repo) > 0 {
 | |
| 		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)
 | |
| 
 | |
| 	tags := []string{} // Note: if empty, all tags will be removed from the image.
 | |
| 	repo := r.Form.Get("repo")
 | |
| 	tag := r.Form.Get("tag")
 | |
| 
 | |
| 	// Do the parameter dance.
 | |
| 	switch {
 | |
| 	// If tag is set, repo must be as well.
 | |
| 	case len(repo) == 0 && len(tag) > 0:
 | |
| 		utils.Error(w, "repo tag is required", http.StatusBadRequest, errors.New("repo parameter is required to tag an image"))
 | |
| 		return
 | |
| 
 | |
| 	case len(repo) == 0:
 | |
| 		break
 | |
| 
 | |
| 	// If repo is specified, we need to add that to the tags.
 | |
| 	default:
 | |
| 		if len(tag) == 0 {
 | |
| 			// Normalize tag to "latest" if empty.
 | |
| 			tag = "latest"
 | |
| 		}
 | |
| 		tags = append(tags, fmt.Sprintf("%s:%s", repo, tag))
 | |
| 	}
 | |
| 
 | |
| 	// Now use the ABI implementation to prevent us from having duplicate
 | |
| 	// code.
 | |
| 	opts := entities.ImageUntagOptions{}
 | |
| 	imageEngine := abi.ImageEngine{Libpod: runtime}
 | |
| 
 | |
| 	name := utils.GetName(r)
 | |
| 	if err := imageEngine.Untag(r.Context(), name, tags, opts); err != nil {
 | |
| 		if errors.Cause(err) == define.ErrNoSuchImage {
 | |
| 			utils.ImageNotFound(w, name, errors.Wrapf(err, "failed to find image %s", name))
 | |
| 		} else {
 | |
| 			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"`
 | |
| 		NoTrunc   bool                `json:"noTrunc"`
 | |
| 		Filters   map[string][]string `json:"filters"`
 | |
| 		TLSVerify bool                `json:"tlsVerify"`
 | |
| 		ListTags  bool                `json:"listTags"`
 | |
| 	}{
 | |
| 		// 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
 | |
| 	}
 | |
| 
 | |
| 	filter := image.SearchFilter{}
 | |
| 	if len(query.Filters) > 0 {
 | |
| 		if len(query.Filters["stars"]) > 0 {
 | |
| 			stars, err := strconv.Atoi(query.Filters["stars"][0])
 | |
| 			if err != nil {
 | |
| 				utils.InternalServerError(w, err)
 | |
| 				return
 | |
| 			}
 | |
| 			filter.Stars = stars
 | |
| 		}
 | |
| 		if len(query.Filters["is-official"]) > 0 {
 | |
| 			isOfficial, err := strconv.ParseBool(query.Filters["is-official"][0])
 | |
| 			if err != nil {
 | |
| 				utils.InternalServerError(w, err)
 | |
| 				return
 | |
| 			}
 | |
| 			filter.IsOfficial = types.NewOptionalBool(isOfficial)
 | |
| 		}
 | |
| 		if len(query.Filters["is-automated"]) > 0 {
 | |
| 			isAutomated, err := strconv.ParseBool(query.Filters["is-automated"][0])
 | |
| 			if err != nil {
 | |
| 				utils.InternalServerError(w, err)
 | |
| 				return
 | |
| 			}
 | |
| 			filter.IsAutomated = types.NewOptionalBool(isAutomated)
 | |
| 		}
 | |
| 	}
 | |
| 	options := image.SearchOptions{
 | |
| 		Limit:    query.Limit,
 | |
| 		NoTrunc:  query.NoTrunc,
 | |
| 		ListTags: query.ListTags,
 | |
| 		Filter:   filter,
 | |
| 	}
 | |
| 
 | |
| 	if _, found := r.URL.Query()["tlsVerify"]; found {
 | |
| 		options.InsecureSkipTLSVerify = types.NewOptionalBool(!query.TLSVerify)
 | |
| 	}
 | |
| 
 | |
| 	_, 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()))
 | |
| 		return
 | |
| 	}
 | |
| 	defer auth.RemoveAuthfile(authfile)
 | |
| 	options.Authfile = authfile
 | |
| 
 | |
| 	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].Description
 | |
| 		reports[i].Stars = searchResults[i].Stars
 | |
| 		reports[i].Official = searchResults[i].Official
 | |
| 		reports[i].Automated = searchResults[i].Automated
 | |
| 		reports[i].Tag = searchResults[i].Tag
 | |
| 	}
 | |
| 
 | |
| 	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"`
 | |
| 	}{}
 | |
| 
 | |
| 	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))
 | |
| 	}
 | |
| }
 |