github.com/hanks177/podman/v4@v4.1.3-0.20220613032544-16d90015bc83/pkg/api/handlers/libpod/manifests.go (about)

     1  package libpod
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io/ioutil"
     8  	"net/http"
     9  	"net/url"
    10  	"strconv"
    11  	"strings"
    12  
    13  	"github.com/containers/image/v5/docker/reference"
    14  	"github.com/containers/image/v5/manifest"
    15  	"github.com/containers/image/v5/types"
    16  	"github.com/hanks177/podman/v4/libpod"
    17  	"github.com/hanks177/podman/v4/pkg/api/handlers"
    18  	"github.com/hanks177/podman/v4/pkg/api/handlers/utils"
    19  	api "github.com/hanks177/podman/v4/pkg/api/types"
    20  	"github.com/hanks177/podman/v4/pkg/auth"
    21  	"github.com/hanks177/podman/v4/pkg/domain/entities"
    22  	"github.com/hanks177/podman/v4/pkg/domain/infra/abi"
    23  	"github.com/hanks177/podman/v4/pkg/errorhandling"
    24  	"github.com/gorilla/mux"
    25  	"github.com/gorilla/schema"
    26  	"github.com/opencontainers/go-digest"
    27  	"github.com/pkg/errors"
    28  )
    29  
    30  func ManifestCreate(w http.ResponseWriter, r *http.Request) {
    31  	runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
    32  	decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder)
    33  	query := struct {
    34  		Name   string   `schema:"name"`
    35  		Images []string `schema:"images"`
    36  		All    bool     `schema:"all"`
    37  	}{
    38  		// Add defaults here once needed.
    39  	}
    40  
    41  	// Support 3.x API calls, alias image to images
    42  	if image, ok := r.URL.Query()["image"]; ok {
    43  		query.Images = image
    44  	}
    45  
    46  	if err := decoder.Decode(&query, r.URL.Query()); err != nil {
    47  		utils.Error(w, http.StatusBadRequest,
    48  			errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String()))
    49  		return
    50  	}
    51  
    52  	// Support 4.x API calls, map query parameter to path
    53  	if name, ok := mux.Vars(r)["name"]; ok {
    54  		n, err := url.QueryUnescape(name)
    55  		if err != nil {
    56  			utils.Error(w, http.StatusBadRequest,
    57  				errors.Wrapf(err, "failed to parse name parameter %q", name))
    58  			return
    59  		}
    60  		query.Name = n
    61  	}
    62  
    63  	if _, err := reference.ParseNormalizedNamed(query.Name); err != nil {
    64  		utils.Error(w, http.StatusBadRequest,
    65  			errors.Wrapf(err, "invalid image name %s", query.Name))
    66  		return
    67  	}
    68  
    69  	imageEngine := abi.ImageEngine{Libpod: runtime}
    70  
    71  	createOptions := entities.ManifestCreateOptions{All: query.All}
    72  	manID, err := imageEngine.ManifestCreate(r.Context(), query.Name, query.Images, createOptions)
    73  	if err != nil {
    74  		utils.InternalServerError(w, err)
    75  		return
    76  	}
    77  
    78  	status := http.StatusOK
    79  	if _, err := utils.SupportedVersion(r, "< 4.0.0"); err == utils.ErrVersionNotSupported {
    80  		status = http.StatusCreated
    81  	}
    82  
    83  	buffer, err := ioutil.ReadAll(r.Body)
    84  	if err != nil {
    85  		utils.InternalServerError(w, err)
    86  		return
    87  	}
    88  
    89  	// Treat \r\n as empty body
    90  	if len(buffer) < 3 {
    91  		utils.WriteResponse(w, status, entities.IDResponse{ID: manID})
    92  		return
    93  	}
    94  
    95  	body := new(entities.ManifestModifyOptions)
    96  	if err := json.Unmarshal(buffer, body); err != nil {
    97  		utils.InternalServerError(w, errors.Wrap(err, "Decode()"))
    98  		return
    99  	}
   100  
   101  	// gather all images for manifest list
   102  	var images []string
   103  	if len(query.Images) > 0 {
   104  		images = query.Images
   105  	}
   106  	if len(body.Images) > 0 {
   107  		images = body.Images
   108  	}
   109  
   110  	id, err := imageEngine.ManifestAdd(r.Context(), query.Name, images, body.ManifestAddOptions)
   111  	if err != nil {
   112  		utils.InternalServerError(w, err)
   113  		return
   114  	}
   115  
   116  	utils.WriteResponse(w, status, entities.IDResponse{ID: id})
   117  }
   118  
   119  // ManifestExists return true if manifest list exists.
   120  func ManifestExists(w http.ResponseWriter, r *http.Request) {
   121  	runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
   122  	name := utils.GetName(r)
   123  
   124  	imageEngine := abi.ImageEngine{Libpod: runtime}
   125  	report, err := imageEngine.ManifestExists(r.Context(), name)
   126  	if err != nil {
   127  		utils.Error(w, http.StatusInternalServerError, err)
   128  		return
   129  	}
   130  	if !report.Value {
   131  		utils.Error(w, http.StatusNotFound, errors.New("manifest not found"))
   132  		return
   133  	}
   134  	utils.WriteResponse(w, http.StatusNoContent, "")
   135  }
   136  
   137  func ManifestInspect(w http.ResponseWriter, r *http.Request) {
   138  	runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
   139  	name := utils.GetName(r)
   140  
   141  	imageEngine := abi.ImageEngine{Libpod: runtime}
   142  	rawManifest, err := imageEngine.ManifestInspect(r.Context(), name)
   143  	if err != nil {
   144  		utils.Error(w, http.StatusNotFound, err)
   145  		return
   146  	}
   147  
   148  	var schema2List manifest.Schema2List
   149  	if err := json.Unmarshal(rawManifest, &schema2List); err != nil {
   150  		utils.Error(w, http.StatusInternalServerError, err)
   151  		return
   152  	}
   153  
   154  	utils.WriteResponse(w, http.StatusOK, schema2List)
   155  }
   156  
   157  // ManifestAddV3 remove digest from manifest list
   158  //
   159  // As of 4.0.0 use ManifestModify instead
   160  func ManifestAddV3(w http.ResponseWriter, r *http.Request) {
   161  	runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
   162  
   163  	// Wrapper to support 3.x with 4.x libpod
   164  	query := struct {
   165  		entities.ManifestAddOptions
   166  		TLSVerify bool `schema:"tlsVerify"`
   167  	}{}
   168  	if err := json.NewDecoder(r.Body).Decode(&query); err != nil {
   169  		utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "Decode()"))
   170  		return
   171  	}
   172  
   173  	authconf, authfile, err := auth.GetCredentials(r)
   174  	if err != nil {
   175  		utils.Error(w, http.StatusBadRequest, err)
   176  		return
   177  	}
   178  	defer auth.RemoveAuthfile(authfile)
   179  	var username, password string
   180  	if authconf != nil {
   181  		username = authconf.Username
   182  		password = authconf.Password
   183  	}
   184  	query.ManifestAddOptions.Authfile = authfile
   185  	query.ManifestAddOptions.Username = username
   186  	query.ManifestAddOptions.Password = password
   187  	if sys := runtime.SystemContext(); sys != nil {
   188  		query.ManifestAddOptions.CertDir = sys.DockerCertPath
   189  	}
   190  	if _, found := r.URL.Query()["tlsVerify"]; found {
   191  		query.SkipTLSVerify = types.NewOptionalBool(!query.TLSVerify)
   192  	}
   193  
   194  	name := utils.GetName(r)
   195  	if _, err := runtime.LibimageRuntime().LookupManifestList(name); err != nil {
   196  		utils.Error(w, http.StatusNotFound, err)
   197  		return
   198  	}
   199  
   200  	imageEngine := abi.ImageEngine{Libpod: runtime}
   201  	newID, err := imageEngine.ManifestAdd(r.Context(), name, query.Images, query.ManifestAddOptions)
   202  	if err != nil {
   203  		utils.InternalServerError(w, err)
   204  		return
   205  	}
   206  	utils.WriteResponse(w, http.StatusOK, entities.IDResponse{ID: newID})
   207  }
   208  
   209  // ManifestRemoveDigestV3 remove digest from manifest list
   210  //
   211  // As of 4.0.0 use ManifestModify instead
   212  func ManifestRemoveDigestV3(w http.ResponseWriter, r *http.Request) {
   213  	runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
   214  	decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder)
   215  	query := struct {
   216  		Digest string `schema:"digest"`
   217  	}{
   218  		// Add defaults here once needed.
   219  	}
   220  	name := utils.GetName(r)
   221  	if err := decoder.Decode(&query, r.URL.Query()); err != nil {
   222  		utils.Error(w, http.StatusBadRequest,
   223  			errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String()))
   224  		return
   225  	}
   226  	manifestList, err := runtime.LibimageRuntime().LookupManifestList(name)
   227  	if err != nil {
   228  		utils.Error(w, http.StatusNotFound, err)
   229  		return
   230  	}
   231  	d, err := digest.Parse(query.Digest)
   232  	if err != nil {
   233  		utils.Error(w, http.StatusBadRequest, err)
   234  		return
   235  	}
   236  	if err := manifestList.RemoveInstance(d); err != nil {
   237  		utils.InternalServerError(w, err)
   238  		return
   239  	}
   240  	utils.WriteResponse(w, http.StatusOK, entities.IDResponse{ID: manifestList.ID()})
   241  }
   242  
   243  // ManifestPushV3 push image to registry
   244  //
   245  // As of 4.0.0 use ManifestPush instead
   246  func ManifestPushV3(w http.ResponseWriter, r *http.Request) {
   247  	runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
   248  	decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder)
   249  	query := struct {
   250  		All         bool   `schema:"all"`
   251  		Destination string `schema:"destination"`
   252  		TLSVerify   bool   `schema:"tlsVerify"`
   253  	}{
   254  		// Add defaults here once needed.
   255  	}
   256  	if err := decoder.Decode(&query, r.URL.Query()); err != nil {
   257  		utils.Error(w, http.StatusBadRequest,
   258  			errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String()))
   259  		return
   260  	}
   261  	if err := utils.IsRegistryReference(query.Destination); err != nil {
   262  		utils.Error(w, http.StatusBadRequest, err)
   263  		return
   264  	}
   265  
   266  	source := utils.GetName(r)
   267  	authconf, authfile, err := auth.GetCredentials(r)
   268  	if err != nil {
   269  		utils.Error(w, http.StatusBadRequest, err)
   270  		return
   271  	}
   272  	defer auth.RemoveAuthfile(authfile)
   273  	var username, password string
   274  	if authconf != nil {
   275  		username = authconf.Username
   276  		password = authconf.Password
   277  	}
   278  	options := entities.ImagePushOptions{
   279  		Authfile: authfile,
   280  		Username: username,
   281  		Password: password,
   282  		All:      query.All,
   283  	}
   284  	if sys := runtime.SystemContext(); sys != nil {
   285  		options.CertDir = sys.DockerCertPath
   286  	}
   287  	if _, found := r.URL.Query()["tlsVerify"]; found {
   288  		options.SkipTLSVerify = types.NewOptionalBool(!query.TLSVerify)
   289  	}
   290  	imageEngine := abi.ImageEngine{Libpod: runtime}
   291  	digest, err := imageEngine.ManifestPush(context.Background(), source, query.Destination, options)
   292  	if err != nil {
   293  		utils.Error(w, http.StatusBadRequest, errors.Wrapf(err, "error pushing image %q", query.Destination))
   294  		return
   295  	}
   296  	utils.WriteResponse(w, http.StatusOK, entities.IDResponse{ID: digest})
   297  }
   298  
   299  // ManifestPush push image to registry
   300  //
   301  // As of 4.0.0
   302  func ManifestPush(w http.ResponseWriter, r *http.Request) {
   303  	runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
   304  	decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder)
   305  
   306  	query := struct {
   307  		All       bool `schema:"all"`
   308  		TLSVerify bool `schema:"tlsVerify"`
   309  	}{
   310  		// Add defaults here once needed.
   311  	}
   312  	if err := decoder.Decode(&query, r.URL.Query()); err != nil {
   313  		utils.Error(w, http.StatusBadRequest,
   314  			errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String()))
   315  		return
   316  	}
   317  
   318  	destination := utils.GetVar(r, "destination")
   319  	if err := utils.IsRegistryReference(destination); err != nil {
   320  		utils.Error(w, http.StatusBadRequest, err)
   321  		return
   322  	}
   323  
   324  	authconf, authfile, err := auth.GetCredentials(r)
   325  	if err != nil {
   326  		utils.Error(w, http.StatusBadRequest, errors.Wrapf(err, "failed to parse registry header for %s", r.URL.String()))
   327  		return
   328  	}
   329  	defer auth.RemoveAuthfile(authfile)
   330  	var username, password string
   331  	if authconf != nil {
   332  		username = authconf.Username
   333  		password = authconf.Password
   334  	}
   335  	options := entities.ImagePushOptions{
   336  		Authfile: authfile,
   337  		Username: username,
   338  		Password: password,
   339  		All:      query.All,
   340  	}
   341  	if sys := runtime.SystemContext(); sys != nil {
   342  		options.CertDir = sys.DockerCertPath
   343  	}
   344  	if _, found := r.URL.Query()["tlsVerify"]; found {
   345  		options.SkipTLSVerify = types.NewOptionalBool(!query.TLSVerify)
   346  	}
   347  
   348  	imageEngine := abi.ImageEngine{Libpod: runtime}
   349  	source := utils.GetName(r)
   350  	digest, err := imageEngine.ManifestPush(context.Background(), source, destination, options)
   351  	if err != nil {
   352  		utils.Error(w, http.StatusBadRequest, errors.Wrapf(err, "error pushing image %q", destination))
   353  		return
   354  	}
   355  	utils.WriteResponse(w, http.StatusOK, entities.IDResponse{ID: digest})
   356  }
   357  
   358  // ManifestModify efficiently updates the named manifest list
   359  func ManifestModify(w http.ResponseWriter, r *http.Request) {
   360  	runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
   361  	imageEngine := abi.ImageEngine{Libpod: runtime}
   362  
   363  	body := new(entities.ManifestModifyOptions)
   364  	if err := json.NewDecoder(r.Body).Decode(body); err != nil {
   365  		utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "Decode()"))
   366  		return
   367  	}
   368  
   369  	name := utils.GetName(r)
   370  	if _, err := runtime.LibimageRuntime().LookupManifestList(name); err != nil {
   371  		utils.Error(w, http.StatusNotFound, err)
   372  		return
   373  	}
   374  
   375  	if tlsVerify, ok := r.URL.Query()["tlsVerify"]; ok {
   376  		tls, err := strconv.ParseBool(tlsVerify[len(tlsVerify)-1])
   377  		if err != nil {
   378  			utils.Error(w, http.StatusBadRequest, fmt.Errorf("tlsVerify param is not a bool: %w", err))
   379  			return
   380  		}
   381  		body.SkipTLSVerify = types.NewOptionalBool(!tls)
   382  	}
   383  
   384  	authconf, authfile, err := auth.GetCredentials(r)
   385  	if err != nil {
   386  		utils.Error(w, http.StatusBadRequest, err)
   387  		return
   388  	}
   389  	defer auth.RemoveAuthfile(authfile)
   390  	var username, password string
   391  	if authconf != nil {
   392  		username = authconf.Username
   393  		password = authconf.Password
   394  	}
   395  	body.ManifestAddOptions.Authfile = authfile
   396  	body.ManifestAddOptions.Username = username
   397  	body.ManifestAddOptions.Password = password
   398  	if sys := runtime.SystemContext(); sys != nil {
   399  		body.ManifestAddOptions.CertDir = sys.DockerCertPath
   400  	}
   401  
   402  	var report entities.ManifestModifyReport
   403  	switch {
   404  	case strings.EqualFold("update", body.Operation):
   405  		id, err := imageEngine.ManifestAdd(r.Context(), name, body.Images, body.ManifestAddOptions)
   406  		if err != nil {
   407  			report.Errors = append(report.Errors, err)
   408  			break
   409  		}
   410  		report = entities.ManifestModifyReport{
   411  			ID:     id,
   412  			Images: body.Images,
   413  		}
   414  	case strings.EqualFold("remove", body.Operation):
   415  		for _, image := range body.Images {
   416  			id, err := imageEngine.ManifestRemoveDigest(r.Context(), name, image)
   417  			if err != nil {
   418  				report.Errors = append(report.Errors, err)
   419  				continue
   420  			}
   421  			report.ID = id
   422  			report.Images = append(report.Images, image)
   423  		}
   424  	case strings.EqualFold("annotate", body.Operation):
   425  		options := entities.ManifestAnnotateOptions{
   426  			Annotation: body.Annotation,
   427  			Arch:       body.Arch,
   428  			Features:   body.Features,
   429  			OS:         body.OS,
   430  			OSFeatures: body.OSFeatures,
   431  			OSVersion:  body.OSVersion,
   432  			Variant:    body.Variant,
   433  		}
   434  		for _, image := range body.Images {
   435  			id, err := imageEngine.ManifestAnnotate(r.Context(), name, image, options)
   436  			if err != nil {
   437  				report.Errors = append(report.Errors, err)
   438  				continue
   439  			}
   440  			report.ID = id
   441  			report.Images = append(report.Images, image)
   442  		}
   443  	default:
   444  		utils.Error(w, http.StatusBadRequest, fmt.Errorf("illegal operation %q for %q", body.Operation, r.URL.String()))
   445  		return
   446  	}
   447  
   448  	statusCode := http.StatusOK
   449  	switch {
   450  	case len(report.Errors) > 0 && len(report.Images) > 0:
   451  		statusCode = http.StatusConflict
   452  	case len(report.Errors) > 0:
   453  		statusCode = http.StatusBadRequest
   454  	}
   455  	utils.WriteResponse(w, statusCode, report)
   456  }
   457  
   458  // ManifestDelete removes a manifest list from storage
   459  func ManifestDelete(w http.ResponseWriter, r *http.Request) {
   460  	runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
   461  	imageEngine := abi.ImageEngine{Libpod: runtime}
   462  
   463  	name := utils.GetName(r)
   464  	if _, err := runtime.LibimageRuntime().LookupManifestList(name); err != nil {
   465  		utils.Error(w, http.StatusNotFound, err)
   466  		return
   467  	}
   468  
   469  	results, errs := imageEngine.ManifestRm(r.Context(), []string{name})
   470  	errsString := errorhandling.ErrorsToStrings(errs)
   471  	report := handlers.LibpodImagesRemoveReport{
   472  		ImageRemoveReport: *results,
   473  		Errors:            errsString,
   474  	}
   475  	utils.WriteResponse(w, http.StatusOK, report)
   476  }