mirror of
				https://github.com/containers/podman.git
				synced 2025-10-31 10:00:01 +08:00 
			
		
		
		
	 9ba52e8ef0
			
		
	
	9ba52e8ef0
	
	
	
		
			
			The goal is to improve errors when users use the wrong transport in certain cases we stutter, in other cases we don't give enough information. Remove stutters when failing to pull remote images, because of lack of support. Fix errors returned by reference.Parse to wrap in image that was being checked. Fixes: https://github.com/containers/podman/issues/7116 Signed-off-by: Daniel J Walsh <dwalsh@redhat.com>
		
			
				
	
	
		
			202 lines
		
	
	
		
			6.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			202 lines
		
	
	
		
			6.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| 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/image/v5/types"
 | |
| 	"github.com/containers/podman/v2/libpod"
 | |
| 	"github.com/containers/podman/v2/libpod/image"
 | |
| 	"github.com/containers/podman/v2/pkg/api/handlers/utils"
 | |
| 	"github.com/containers/podman/v2/pkg/auth"
 | |
| 	"github.com/containers/podman/v2/pkg/channel"
 | |
| 	"github.com/containers/podman/v2/pkg/domain/entities"
 | |
| 	"github.com/containers/podman/v2/pkg/util"
 | |
| 	"github.com/gorilla/schema"
 | |
| 	"github.com/pkg/errors"
 | |
| 	"github.com/sirupsen/logrus"
 | |
| )
 | |
| 
 | |
| // 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"`
 | |
| 		OverrideVariant string `schema:"overrideVariant"`
 | |
| 		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, err)
 | |
| 		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, 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)
 | |
| 
 | |
| 	// Setup the registry options
 | |
| 	dockerRegistryOptions := image.DockerRegistryOptions{
 | |
| 		DockerRegistryCreds: authConf,
 | |
| 		OSChoice:            query.OverrideOS,
 | |
| 		ArchitectureChoice:  query.OverrideArch,
 | |
| 		VariantChoice:       query.OverrideVariant,
 | |
| 	}
 | |
| 	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))
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	writer := channel.NewWriter(make(chan []byte, 1))
 | |
| 	defer writer.Close()
 | |
| 
 | |
| 	stderr := channel.NewWriter(make(chan []byte, 1))
 | |
| 	defer stderr.Close()
 | |
| 
 | |
| 	images := make([]string, 0, len(imagesToPull))
 | |
| 	runCtx, cancel := context.WithCancel(context.Background())
 | |
| 	go func(imgs []string) {
 | |
| 		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)
 | |
| 			if err != nil {
 | |
| 				stderr.Write([]byte(err.Error() + "\n"))
 | |
| 			} else {
 | |
| 				images = append(images, newImage.ID())
 | |
| 			}
 | |
| 		}
 | |
| 	}(imagesToPull)
 | |
| 
 | |
| 	flush := func() {
 | |
| 		if flusher, ok := w.(http.Flusher); ok {
 | |
| 			flusher.Flush()
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	w.WriteHeader(http.StatusOK)
 | |
| 	w.Header().Add("Content-Type", "application/json")
 | |
| 	flush()
 | |
| 
 | |
| 	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)
 | |
| 			if err := enc.Encode(report); err != nil {
 | |
| 				logrus.Warnf("Failed to json encode error %q", err.Error())
 | |
| 			}
 | |
| 			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
 | |
| 				// 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()
 | |
| 			}
 | |
| 			break loop // break out of for/select infinite loop
 | |
| 		case <-r.Context().Done():
 | |
| 			// Client has closed connection
 | |
| 			break loop // break out of for/select infinite loop
 | |
| 		}
 | |
| 	}
 | |
| }
 |