mirror of https://github.com/containers/podman.git
				
				
				
			
		
			
				
	
	
		
			148 lines
		
	
	
		
			4.0 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			148 lines
		
	
	
		
			4.0 KiB
		
	
	
	
		
			Go
		
	
	
	
| package libpod
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"encoding/json"
 | |
| 	"fmt"
 | |
| 	"net/http"
 | |
| 	"strings"
 | |
| 
 | |
| 	"github.com/containers/image/v5/types"
 | |
| 	"github.com/containers/podman/v4/libpod"
 | |
| 	"github.com/containers/podman/v4/pkg/api/handlers/utils"
 | |
| 	api "github.com/containers/podman/v4/pkg/api/types"
 | |
| 	"github.com/containers/podman/v4/pkg/auth"
 | |
| 	"github.com/containers/podman/v4/pkg/channel"
 | |
| 	"github.com/containers/podman/v4/pkg/domain/entities"
 | |
| 	"github.com/containers/podman/v4/pkg/domain/infra/abi"
 | |
| 	"github.com/gorilla/schema"
 | |
| 	"github.com/sirupsen/logrus"
 | |
| )
 | |
| 
 | |
| // PushImage is the handler for the compat http endpoint for pushing images.
 | |
| func PushImage(w http.ResponseWriter, r *http.Request) {
 | |
| 	decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder)
 | |
| 	runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
 | |
| 
 | |
| 	query := struct {
 | |
| 		All               bool   `schema:"all"`
 | |
| 		CompressionFormat string `schema:"compressionFormat"`
 | |
| 		Destination       string `schema:"destination"`
 | |
| 		Format            string `schema:"format"`
 | |
| 		RemoveSignatures  bool   `schema:"removeSignatures"`
 | |
| 		TLSVerify         bool   `schema:"tlsVerify"`
 | |
| 		Quiet             bool   `schema:"quiet"`
 | |
| 	}{
 | |
| 		TLSVerify: true,
 | |
| 		// #14971: older versions did not sent *any* data, so we need
 | |
| 		//         to be quiet by default to remain backwards compatible
 | |
| 		Quiet: true,
 | |
| 	}
 | |
| 	if err := decoder.Decode(&query, r.URL.Query()); err != nil {
 | |
| 		utils.Error(w, http.StatusBadRequest, fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err))
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	source := strings.TrimSuffix(utils.GetName(r), "/push") // GetName returns the entire path
 | |
| 	if _, err := utils.ParseStorageReference(source); err != nil {
 | |
| 		utils.Error(w, http.StatusBadRequest, err)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	destination := query.Destination
 | |
| 	if destination == "" {
 | |
| 		destination = source
 | |
| 	}
 | |
| 
 | |
| 	if err := utils.IsRegistryReference(destination); err != nil {
 | |
| 		utils.Error(w, http.StatusBadRequest, err)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	authconf, authfile, err := auth.GetCredentials(r)
 | |
| 	if err != nil {
 | |
| 		utils.Error(w, http.StatusBadRequest, err)
 | |
| 		return
 | |
| 	}
 | |
| 	defer auth.RemoveAuthfile(authfile)
 | |
| 
 | |
| 	var username, password string
 | |
| 	if authconf != nil {
 | |
| 		username = authconf.Username
 | |
| 		password = authconf.Password
 | |
| 	}
 | |
| 	options := entities.ImagePushOptions{
 | |
| 		All:               query.All,
 | |
| 		Authfile:          authfile,
 | |
| 		CompressionFormat: query.CompressionFormat,
 | |
| 		Format:            query.Format,
 | |
| 		Password:          password,
 | |
| 		Quiet:             true,
 | |
| 		RemoveSignatures:  query.RemoveSignatures,
 | |
| 		Username:          username,
 | |
| 	}
 | |
| 
 | |
| 	if _, found := r.URL.Query()["tlsVerify"]; found {
 | |
| 		options.SkipTLSVerify = types.NewOptionalBool(!query.TLSVerify)
 | |
| 	}
 | |
| 
 | |
| 	imageEngine := abi.ImageEngine{Libpod: runtime}
 | |
| 
 | |
| 	// Let's keep thing simple when running in quiet mode and push directly.
 | |
| 	if query.Quiet {
 | |
| 		if err := imageEngine.Push(context.Background(), source, destination, options); err != nil {
 | |
| 			utils.Error(w, http.StatusBadRequest, fmt.Errorf("error pushing image %q: %w", destination, err))
 | |
| 			return
 | |
| 		}
 | |
| 		utils.WriteResponse(w, http.StatusOK, "")
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	writer := channel.NewWriter(make(chan []byte))
 | |
| 	defer writer.Close()
 | |
| 	options.Writer = writer
 | |
| 
 | |
| 	pushCtx, pushCancel := context.WithCancel(r.Context())
 | |
| 	var pushError error
 | |
| 	go func() {
 | |
| 		defer pushCancel()
 | |
| 		pushError = imageEngine.Push(pushCtx, source, destination, options)
 | |
| 	}()
 | |
| 
 | |
| 	flush := func() {
 | |
| 		if flusher, ok := w.(http.Flusher); ok {
 | |
| 			flusher.Flush()
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	w.WriteHeader(http.StatusOK)
 | |
| 	w.Header().Set("Content-Type", "application/json")
 | |
| 	flush()
 | |
| 
 | |
| 	enc := json.NewEncoder(w)
 | |
| 	enc.SetEscapeHTML(true)
 | |
| 	for {
 | |
| 		var report entities.ImagePushReport
 | |
| 		select {
 | |
| 		case s := <-writer.Chan():
 | |
| 			report.Stream = string(s)
 | |
| 			if err := enc.Encode(report); err != nil {
 | |
| 				logrus.Warnf("Failed to encode json: %v", err)
 | |
| 			}
 | |
| 			flush()
 | |
| 		case <-pushCtx.Done():
 | |
| 			if pushError != nil {
 | |
| 				report.Error = pushError.Error()
 | |
| 				if err := enc.Encode(report); err != nil {
 | |
| 					logrus.Warnf("Failed to encode json: %v", err)
 | |
| 				}
 | |
| 			}
 | |
| 			flush()
 | |
| 			return
 | |
| 		case <-r.Context().Done():
 | |
| 			// Client has closed connection
 | |
| 			return
 | |
| 		}
 | |
| 	}
 | |
| }
 |