mirror of
				https://github.com/containers/podman.git
				synced 2025-10-31 10:00:01 +08:00 
			
		
		
		
	 8d4e19634c
			
		
	
	8d4e19634c
	
	
	
		
			
			Begin the migration of the image bindings for podman 3.0. this includes the use of options for each binding. build was intentionally not converted as I believe it needs more discussion before migration. specifically, the build options themselves. also noteworthly is that the remove image and remove images bindings were merged into one. the remove images (or batch remove) has one downside in that the errors return no longer adhere to http return codes. this should be discussed and reimplemented in subsequent code. 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 tempdir"))
 | |
| 			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))
 | |
| 	}
 | |
| }
 |