github.com/containers/podman/v2@v2.2.2-0.20210501105131-c1e07d070c4c/pkg/api/handlers/libpod/images.go (about)

     1  package libpod
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io"
     7  	"io/ioutil"
     8  	"net/http"
     9  	"os"
    10  	"strconv"
    11  	"strings"
    12  
    13  	"github.com/containers/buildah"
    14  	"github.com/containers/image/v5/manifest"
    15  	"github.com/containers/image/v5/types"
    16  	"github.com/containers/podman/v2/libpod"
    17  	"github.com/containers/podman/v2/libpod/define"
    18  	"github.com/containers/podman/v2/libpod/image"
    19  	image2 "github.com/containers/podman/v2/libpod/image"
    20  	"github.com/containers/podman/v2/pkg/api/handlers"
    21  	"github.com/containers/podman/v2/pkg/api/handlers/utils"
    22  	"github.com/containers/podman/v2/pkg/auth"
    23  	"github.com/containers/podman/v2/pkg/domain/entities"
    24  	"github.com/containers/podman/v2/pkg/domain/infra/abi"
    25  	"github.com/containers/podman/v2/pkg/errorhandling"
    26  	utils2 "github.com/containers/podman/v2/utils"
    27  	"github.com/gorilla/schema"
    28  	"github.com/pkg/errors"
    29  	"github.com/sirupsen/logrus"
    30  )
    31  
    32  // Commit
    33  // author string
    34  // "container"
    35  // repo string
    36  // tag string
    37  // message
    38  // pause bool
    39  // changes []string
    40  
    41  // create
    42  
    43  func ImageExists(w http.ResponseWriter, r *http.Request) {
    44  	runtime := r.Context().Value("runtime").(*libpod.Runtime)
    45  	name := utils.GetName(r)
    46  
    47  	ir := abi.ImageEngine{Libpod: runtime}
    48  	report, err := ir.Exists(r.Context(), name)
    49  	if err != nil {
    50  		utils.Error(w, "Something went wrong.", http.StatusNotFound, errors.Wrapf(err, "failed to find image %s", name))
    51  		return
    52  	}
    53  	if !report.Value {
    54  		utils.Error(w, "Something went wrong.", http.StatusNotFound, errors.Wrapf(nil, "failed to find image %s", name))
    55  		return
    56  	}
    57  	utils.WriteResponse(w, http.StatusNoContent, "")
    58  }
    59  
    60  func ImageTree(w http.ResponseWriter, r *http.Request) {
    61  	runtime := r.Context().Value("runtime").(*libpod.Runtime)
    62  	name := utils.GetName(r)
    63  	decoder := r.Context().Value("decoder").(*schema.Decoder)
    64  	query := struct {
    65  		WhatRequires bool `schema:"whatrequires"`
    66  	}{
    67  		WhatRequires: false,
    68  	}
    69  	if err := decoder.Decode(&query, r.URL.Query()); err != nil {
    70  		utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
    71  			errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String()))
    72  		return
    73  	}
    74  	ir := abi.ImageEngine{Libpod: runtime}
    75  	options := entities.ImageTreeOptions{WhatRequires: query.WhatRequires}
    76  	report, err := ir.Tree(r.Context(), name, options)
    77  	if err != nil {
    78  		if errors.Cause(err) == define.ErrNoSuchImage {
    79  			utils.Error(w, "Something went wrong.", http.StatusNotFound, errors.Wrapf(err, "failed to find image %s", name))
    80  			return
    81  		}
    82  		utils.Error(w, "Server error", http.StatusInternalServerError, errors.Wrapf(err, "failed to generate image tree for %s", name))
    83  		return
    84  	}
    85  	utils.WriteResponse(w, http.StatusOK, report)
    86  }
    87  
    88  func GetImage(w http.ResponseWriter, r *http.Request) {
    89  	name := utils.GetName(r)
    90  	newImage, err := utils.GetImage(r, name)
    91  	if err != nil {
    92  		utils.Error(w, "Something went wrong.", http.StatusNotFound, errors.Wrapf(err, "failed to find image %s", name))
    93  		return
    94  	}
    95  	inspect, err := newImage.Inspect(r.Context())
    96  	if err != nil {
    97  		utils.Error(w, "Server error", http.StatusInternalServerError, errors.Wrapf(err, "failed in inspect image %s", inspect.ID))
    98  		return
    99  	}
   100  	utils.WriteResponse(w, http.StatusOK, inspect)
   101  }
   102  
   103  func GetImages(w http.ResponseWriter, r *http.Request) {
   104  	images, err := utils.GetImages(w, r)
   105  	if err != nil {
   106  		utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Failed get images"))
   107  		return
   108  	}
   109  	var summaries = make([]*entities.ImageSummary, len(images))
   110  	for j, img := range images {
   111  		is, err := handlers.ImageToImageSummary(img)
   112  		if err != nil {
   113  			utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Failed transform image summaries"))
   114  			return
   115  		}
   116  		// libpod has additional fields that we need to populate.
   117  		is.ReadOnly = img.IsReadOnly()
   118  		summaries[j] = is
   119  	}
   120  	utils.WriteResponse(w, http.StatusOK, summaries)
   121  }
   122  
   123  func PruneImages(w http.ResponseWriter, r *http.Request) {
   124  	var (
   125  		err error
   126  	)
   127  	runtime := r.Context().Value("runtime").(*libpod.Runtime)
   128  	decoder := r.Context().Value("decoder").(*schema.Decoder)
   129  	query := struct {
   130  		All     bool                `schema:"all"`
   131  		Filters map[string][]string `schema:"filters"`
   132  	}{
   133  		// override any golang type defaults
   134  	}
   135  
   136  	if err := decoder.Decode(&query, r.URL.Query()); err != nil {
   137  		utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
   138  			errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String()))
   139  		return
   140  	}
   141  
   142  	var libpodFilters = []string{}
   143  	if _, found := r.URL.Query()["filters"]; found {
   144  		dangling := query.Filters["all"]
   145  		if len(dangling) > 0 {
   146  			query.All, err = strconv.ParseBool(query.Filters["all"][0])
   147  			if err != nil {
   148  				utils.InternalServerError(w, err)
   149  				return
   150  			}
   151  		}
   152  		// dangling is special and not implemented in the libpod side of things
   153  		delete(query.Filters, "dangling")
   154  		for k, v := range query.Filters {
   155  			libpodFilters = append(libpodFilters, fmt.Sprintf("%s=%s", k, v[0]))
   156  		}
   157  	}
   158  
   159  	cids, err := runtime.ImageRuntime().PruneImages(r.Context(), query.All, libpodFilters)
   160  	if err != nil {
   161  		utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err)
   162  		return
   163  	}
   164  	utils.WriteResponse(w, http.StatusOK, cids)
   165  }
   166  
   167  func ExportImage(w http.ResponseWriter, r *http.Request) {
   168  	var (
   169  		output string
   170  	)
   171  	runtime := r.Context().Value("runtime").(*libpod.Runtime)
   172  	decoder := r.Context().Value("decoder").(*schema.Decoder)
   173  	query := struct {
   174  		Compress bool   `schema:"compress"`
   175  		Format   string `schema:"format"`
   176  	}{
   177  		Format: define.OCIArchive,
   178  	}
   179  
   180  	if err := decoder.Decode(&query, r.URL.Query()); err != nil {
   181  		utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
   182  			errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String()))
   183  		return
   184  	}
   185  	name := utils.GetName(r)
   186  	newImage, err := runtime.ImageRuntime().NewFromLocal(name)
   187  	if err != nil {
   188  		utils.ImageNotFound(w, name, err)
   189  		return
   190  	}
   191  	switch query.Format {
   192  	case define.OCIArchive, define.V2s2Archive:
   193  		tmpfile, err := ioutil.TempFile("", "api.tar")
   194  		if err != nil {
   195  			utils.Error(w, "unable to create tmpfile", http.StatusInternalServerError, errors.Wrap(err, "unable to create tempfile"))
   196  			return
   197  		}
   198  		output = tmpfile.Name()
   199  		if err := tmpfile.Close(); err != nil {
   200  			utils.Error(w, "unable to close tmpfile", http.StatusInternalServerError, errors.Wrap(err, "unable to close tempfile"))
   201  			return
   202  		}
   203  	case define.OCIManifestDir, define.V2s2ManifestDir:
   204  		tmpdir, err := ioutil.TempDir("", "save")
   205  		if err != nil {
   206  			utils.Error(w, "unable to create tmpdir", http.StatusInternalServerError, errors.Wrap(err, "unable to create tempdir"))
   207  			return
   208  		}
   209  		output = tmpdir
   210  	default:
   211  		utils.Error(w, "unknown format", http.StatusInternalServerError, errors.Errorf("unknown format %q", query.Format))
   212  		return
   213  	}
   214  	if err := newImage.Save(r.Context(), name, query.Format, output, []string{}, false, query.Compress, true); err != nil {
   215  		utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, err)
   216  		return
   217  	}
   218  	defer os.RemoveAll(output)
   219  	// if dir format, we need to tar it
   220  	if query.Format == "oci-dir" || query.Format == "docker-dir" {
   221  		rdr, err := utils2.Tar(output)
   222  		if err != nil {
   223  			utils.InternalServerError(w, err)
   224  			return
   225  		}
   226  		defer rdr.Close()
   227  		utils.WriteResponse(w, http.StatusOK, rdr)
   228  		return
   229  	}
   230  	rdr, err := os.Open(output)
   231  	if err != nil {
   232  		utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to read the exported tarfile"))
   233  		return
   234  	}
   235  	defer rdr.Close()
   236  	utils.WriteResponse(w, http.StatusOK, rdr)
   237  }
   238  
   239  func ExportImages(w http.ResponseWriter, r *http.Request) {
   240  	var (
   241  		output string
   242  	)
   243  	runtime := r.Context().Value("runtime").(*libpod.Runtime)
   244  	decoder := r.Context().Value("decoder").(*schema.Decoder)
   245  	query := struct {
   246  		Compress   bool     `schema:"compress"`
   247  		Format     string   `schema:"format"`
   248  		References []string `schema:"references"`
   249  	}{
   250  		Format: define.OCIArchive,
   251  	}
   252  
   253  	if err := decoder.Decode(&query, r.URL.Query()); err != nil {
   254  		utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
   255  			errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String()))
   256  		return
   257  	}
   258  
   259  	// References are mandatory!
   260  	if len(query.References) == 0 {
   261  		utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
   262  			errors.New("No references"))
   263  		return
   264  	}
   265  
   266  	// Format is mandatory! Currently, we only support multi-image docker
   267  	// archives.
   268  	switch query.Format {
   269  	case define.V2s2Archive:
   270  		tmpfile, err := ioutil.TempFile("", "api.tar")
   271  		if err != nil {
   272  			utils.Error(w, "unable to create tmpfile", http.StatusInternalServerError, errors.Wrap(err, "unable to create tempfile"))
   273  			return
   274  		}
   275  		output = tmpfile.Name()
   276  		if err := tmpfile.Close(); err != nil {
   277  			utils.Error(w, "unable to close tmpfile", http.StatusInternalServerError, errors.Wrap(err, "unable to close tempfile"))
   278  			return
   279  		}
   280  	default:
   281  		utils.Error(w, "unsupported format", http.StatusInternalServerError, errors.Errorf("unsupported format %q", query.Format))
   282  		return
   283  	}
   284  	defer os.RemoveAll(output)
   285  
   286  	// Use the ABI image engine to share as much code as possible.
   287  	opts := entities.ImageSaveOptions{
   288  		Compress:          query.Compress,
   289  		Format:            query.Format,
   290  		MultiImageArchive: true,
   291  		Output:            output,
   292  		RemoveSignatures:  true,
   293  	}
   294  
   295  	imageEngine := abi.ImageEngine{Libpod: runtime}
   296  	if err := imageEngine.Save(r.Context(), query.References[0], query.References[1:], opts); err != nil {
   297  		utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, err)
   298  		return
   299  	}
   300  
   301  	rdr, err := os.Open(output)
   302  	if err != nil {
   303  		utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to read the exported tarfile"))
   304  		return
   305  	}
   306  	defer rdr.Close()
   307  	utils.WriteResponse(w, http.StatusOK, rdr)
   308  }
   309  
   310  func ImagesLoad(w http.ResponseWriter, r *http.Request) {
   311  	runtime := r.Context().Value("runtime").(*libpod.Runtime)
   312  	decoder := r.Context().Value("decoder").(*schema.Decoder)
   313  	query := struct {
   314  		Reference string `schema:"reference"`
   315  	}{
   316  		// Add defaults here once needed.
   317  	}
   318  
   319  	if err := decoder.Decode(&query, r.URL.Query()); err != nil {
   320  		utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
   321  			errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String()))
   322  		return
   323  	}
   324  
   325  	tmpfile, err := ioutil.TempFile("", "libpod-images-load.tar")
   326  	if err != nil {
   327  		utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to create tempfile"))
   328  		return
   329  	}
   330  	defer os.Remove(tmpfile.Name())
   331  	defer tmpfile.Close()
   332  
   333  	if _, err := io.Copy(tmpfile, r.Body); err != nil && err != io.EOF {
   334  		utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to write archive to temporary file"))
   335  		return
   336  	}
   337  
   338  	tmpfile.Close()
   339  	loadedImage, err := runtime.LoadImage(context.Background(), query.Reference, tmpfile.Name(), os.Stderr, "")
   340  	if err != nil {
   341  		utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to load image"))
   342  		return
   343  	}
   344  	split := strings.Split(loadedImage, ",")
   345  	newImage, err := runtime.ImageRuntime().NewFromLocal(split[0])
   346  	if err != nil {
   347  		utils.InternalServerError(w, err)
   348  		return
   349  	}
   350  	// TODO this should go into libpod proper at some point.
   351  	if len(query.Reference) > 0 {
   352  		if err := newImage.TagImage(query.Reference); err != nil {
   353  			utils.InternalServerError(w, err)
   354  			return
   355  		}
   356  	}
   357  	utils.WriteResponse(w, http.StatusOK, entities.ImageLoadReport{Names: split})
   358  }
   359  
   360  func ImagesImport(w http.ResponseWriter, r *http.Request) {
   361  	runtime := r.Context().Value("runtime").(*libpod.Runtime)
   362  	decoder := r.Context().Value("decoder").(*schema.Decoder)
   363  	query := struct {
   364  		Changes   []string `schema:"changes"`
   365  		Message   string   `schema:"message"`
   366  		Reference string   `schema:"reference"`
   367  		URL       string   `schema:"URL"`
   368  	}{
   369  		// Add defaults here once needed.
   370  	}
   371  
   372  	if err := decoder.Decode(&query, r.URL.Query()); err != nil {
   373  		utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
   374  			errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String()))
   375  		return
   376  	}
   377  
   378  	// Check if we need to load the image from a URL or from the request's body.
   379  	source := query.URL
   380  	if len(query.URL) == 0 {
   381  		tmpfile, err := ioutil.TempFile("", "libpod-images-import.tar")
   382  		if err != nil {
   383  			utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to create tempfile"))
   384  			return
   385  		}
   386  		defer os.Remove(tmpfile.Name())
   387  		defer tmpfile.Close()
   388  
   389  		if _, err := io.Copy(tmpfile, r.Body); err != nil && err != io.EOF {
   390  			utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to write archive to temporary file"))
   391  			return
   392  		}
   393  
   394  		tmpfile.Close()
   395  		source = tmpfile.Name()
   396  	}
   397  	importedImage, err := runtime.Import(context.Background(), source, query.Reference, "", query.Changes, query.Message, true)
   398  	if err != nil {
   399  		utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to import image"))
   400  		return
   401  	}
   402  
   403  	utils.WriteResponse(w, http.StatusOK, entities.ImageImportReport{Id: importedImage})
   404  }
   405  
   406  // PushImage is the handler for the compat http endpoint for pushing images.
   407  func PushImage(w http.ResponseWriter, r *http.Request) {
   408  	decoder := r.Context().Value("decoder").(*schema.Decoder)
   409  	runtime := r.Context().Value("runtime").(*libpod.Runtime)
   410  
   411  	query := struct {
   412  		Destination string `schema:"destination"`
   413  		TLSVerify   bool   `schema:"tlsVerify"`
   414  	}{
   415  		// This is where you can override the golang default value for one of fields
   416  	}
   417  
   418  	if err := decoder.Decode(&query, r.URL.Query()); err != nil {
   419  		utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String()))
   420  		return
   421  	}
   422  
   423  	source := strings.TrimSuffix(utils.GetName(r), "/push") // GetName returns the entire path
   424  	if _, err := utils.ParseStorageReference(source); err != nil {
   425  		utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, err)
   426  		return
   427  	}
   428  
   429  	destination := query.Destination
   430  	if destination == "" {
   431  		destination = source
   432  	}
   433  
   434  	if _, err := utils.ParseDockerReference(destination); err != nil {
   435  		utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, err)
   436  		return
   437  	}
   438  
   439  	newImage, err := runtime.ImageRuntime().NewFromLocal(source)
   440  	if err != nil {
   441  		utils.ImageNotFound(w, source, errors.Wrapf(err, "failed to find image %s", source))
   442  		return
   443  	}
   444  
   445  	authConf, authfile, key, err := auth.GetCredentials(r)
   446  	if err != nil {
   447  		utils.Error(w, "failed to retrieve repository credentials", http.StatusBadRequest, errors.Wrapf(err, "failed to parse %q header for %s", key, r.URL.String()))
   448  		return
   449  	}
   450  	defer auth.RemoveAuthfile(authfile)
   451  	logrus.Errorf("AuthConf: %v", authConf)
   452  
   453  	dockerRegistryOptions := &image.DockerRegistryOptions{
   454  		DockerRegistryCreds: authConf,
   455  	}
   456  	if sys := runtime.SystemContext(); sys != nil {
   457  		dockerRegistryOptions.DockerCertPath = sys.DockerCertPath
   458  		dockerRegistryOptions.RegistriesConfPath = sys.SystemRegistriesConfPath
   459  	}
   460  	if _, found := r.URL.Query()["tlsVerify"]; found {
   461  		dockerRegistryOptions.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!query.TLSVerify)
   462  	}
   463  
   464  	err = newImage.PushImageToHeuristicDestination(
   465  		context.Background(),
   466  		destination,
   467  		"", // manifest type
   468  		authfile,
   469  		"", // digest file
   470  		"", // signature policy
   471  		os.Stderr,
   472  		false, // force compression
   473  		image.SigningOptions{},
   474  		dockerRegistryOptions,
   475  		nil, // additional tags
   476  	)
   477  	if err != nil {
   478  		utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "error pushing image %q", destination))
   479  		return
   480  	}
   481  
   482  	utils.WriteResponse(w, http.StatusOK, "")
   483  }
   484  
   485  func CommitContainer(w http.ResponseWriter, r *http.Request) {
   486  	var (
   487  		destImage string
   488  		mimeType  string
   489  	)
   490  	decoder := r.Context().Value("decoder").(*schema.Decoder)
   491  	runtime := r.Context().Value("runtime").(*libpod.Runtime)
   492  
   493  	query := struct {
   494  		Author    string   `schema:"author"`
   495  		Changes   []string `schema:"changes"`
   496  		Comment   string   `schema:"comment"`
   497  		Container string   `schema:"container"`
   498  		Format    string   `schema:"format"`
   499  		Pause     bool     `schema:"pause"`
   500  		Repo      string   `schema:"repo"`
   501  		Tag       string   `schema:"tag"`
   502  	}{
   503  		Format: "oci",
   504  	}
   505  
   506  	if err := decoder.Decode(&query, r.URL.Query()); err != nil {
   507  		utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String()))
   508  		return
   509  	}
   510  	rtc, err := runtime.GetConfig()
   511  	if err != nil {
   512  		utils.Error(w, "failed to get runtime config", http.StatusInternalServerError, errors.Wrap(err, "failed to get runtime config"))
   513  		return
   514  	}
   515  	sc := image2.GetSystemContext(rtc.Engine.SignaturePolicyPath, "", false)
   516  	tag := "latest"
   517  	options := libpod.ContainerCommitOptions{
   518  		Pause: true,
   519  	}
   520  	switch query.Format {
   521  	case "oci":
   522  		mimeType = buildah.OCIv1ImageManifest
   523  		if len(query.Comment) > 0 {
   524  			utils.InternalServerError(w, errors.New("messages are only compatible with the docker image format (-f docker)"))
   525  			return
   526  		}
   527  	case "docker":
   528  		mimeType = manifest.DockerV2Schema2MediaType
   529  	default:
   530  		utils.InternalServerError(w, errors.Errorf("unrecognized image format %q", query.Format))
   531  		return
   532  	}
   533  	options.CommitOptions = buildah.CommitOptions{
   534  		SignaturePolicyPath:   rtc.Engine.SignaturePolicyPath,
   535  		ReportWriter:          os.Stderr,
   536  		SystemContext:         sc,
   537  		PreferredManifestType: mimeType,
   538  	}
   539  
   540  	if len(query.Tag) > 0 {
   541  		tag = query.Tag
   542  	}
   543  	options.Message = query.Comment
   544  	options.Author = query.Author
   545  	options.Pause = query.Pause
   546  	options.Changes = query.Changes
   547  	ctr, err := runtime.LookupContainer(query.Container)
   548  	if err != nil {
   549  		utils.Error(w, "failed to lookup container", http.StatusNotFound, err)
   550  		return
   551  	}
   552  
   553  	if len(query.Repo) > 0 {
   554  		destImage = fmt.Sprintf("%s:%s", query.Repo, tag)
   555  	}
   556  	commitImage, err := ctr.Commit(r.Context(), destImage, options)
   557  	if err != nil && !strings.Contains(err.Error(), "is not running") {
   558  		utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrapf(err, "CommitFailure"))
   559  		return
   560  	}
   561  	utils.WriteResponse(w, http.StatusOK, handlers.IDResponse{ID: commitImage.ID()}) // nolint
   562  }
   563  
   564  func UntagImage(w http.ResponseWriter, r *http.Request) {
   565  	runtime := r.Context().Value("runtime").(*libpod.Runtime)
   566  
   567  	tags := []string{} // Note: if empty, all tags will be removed from the image.
   568  	repo := r.Form.Get("repo")
   569  	tag := r.Form.Get("tag")
   570  
   571  	// Do the parameter dance.
   572  	switch {
   573  	// If tag is set, repo must be as well.
   574  	case len(repo) == 0 && len(tag) > 0:
   575  		utils.Error(w, "repo tag is required", http.StatusBadRequest, errors.New("repo parameter is required to tag an image"))
   576  		return
   577  
   578  	case len(repo) == 0:
   579  		break
   580  
   581  	// If repo is specified, we need to add that to the tags.
   582  	default:
   583  		if len(tag) == 0 {
   584  			// Normalize tag to "latest" if empty.
   585  			tag = "latest"
   586  		}
   587  		tags = append(tags, fmt.Sprintf("%s:%s", repo, tag))
   588  	}
   589  
   590  	// Now use the ABI implementation to prevent us from having duplicate
   591  	// code.
   592  	opts := entities.ImageUntagOptions{}
   593  	imageEngine := abi.ImageEngine{Libpod: runtime}
   594  
   595  	name := utils.GetName(r)
   596  	if err := imageEngine.Untag(r.Context(), name, tags, opts); err != nil {
   597  		if errors.Cause(err) == define.ErrNoSuchImage {
   598  			utils.ImageNotFound(w, name, errors.Wrapf(err, "failed to find image %s", name))
   599  		} else {
   600  			utils.Error(w, "failed to untag", http.StatusInternalServerError, err)
   601  		}
   602  		return
   603  	}
   604  	utils.WriteResponse(w, http.StatusCreated, "")
   605  }
   606  
   607  func SearchImages(w http.ResponseWriter, r *http.Request) {
   608  	decoder := r.Context().Value("decoder").(*schema.Decoder)
   609  	query := struct {
   610  		Term      string   `json:"term"`
   611  		Limit     int      `json:"limit"`
   612  		NoTrunc   bool     `json:"noTrunc"`
   613  		Filters   []string `json:"filters"`
   614  		TLSVerify bool     `json:"tlsVerify"`
   615  		ListTags  bool     `json:"listTags"`
   616  	}{
   617  		// This is where you can override the golang default value for one of fields
   618  	}
   619  
   620  	if err := decoder.Decode(&query, r.URL.Query()); err != nil {
   621  		utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String()))
   622  		return
   623  	}
   624  
   625  	options := image.SearchOptions{
   626  		Limit:    query.Limit,
   627  		NoTrunc:  query.NoTrunc,
   628  		ListTags: query.ListTags,
   629  	}
   630  	if _, found := r.URL.Query()["tlsVerify"]; found {
   631  		options.InsecureSkipTLSVerify = types.NewOptionalBool(!query.TLSVerify)
   632  	}
   633  
   634  	if _, found := r.URL.Query()["filters"]; found {
   635  		filter, err := image.ParseSearchFilter(query.Filters)
   636  		if err != nil {
   637  			utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "failed to parse filters parameter for %s", r.URL.String()))
   638  			return
   639  		}
   640  		options.Filter = *filter
   641  	}
   642  
   643  	_, authfile, key, err := auth.GetCredentials(r)
   644  	if err != nil {
   645  		utils.Error(w, "failed to retrieve repository credentials", http.StatusBadRequest, errors.Wrapf(err, "failed to parse %q header for %s", key, r.URL.String()))
   646  		return
   647  	}
   648  	defer auth.RemoveAuthfile(authfile)
   649  	options.Authfile = authfile
   650  
   651  	searchResults, err := image.SearchImages(query.Term, options)
   652  	if err != nil {
   653  		utils.BadRequest(w, "term", query.Term, err)
   654  		return
   655  	}
   656  	// Convert from image.SearchResults to entities.ImageSearchReport. We don't
   657  	// want to leak any low-level packages into the remote client, which
   658  	// requires converting.
   659  	reports := make([]entities.ImageSearchReport, len(searchResults))
   660  	for i := range searchResults {
   661  		reports[i].Index = searchResults[i].Index
   662  		reports[i].Name = searchResults[i].Name
   663  		reports[i].Description = searchResults[i].Description
   664  		reports[i].Stars = searchResults[i].Stars
   665  		reports[i].Official = searchResults[i].Official
   666  		reports[i].Automated = searchResults[i].Automated
   667  		reports[i].Tag = searchResults[i].Tag
   668  	}
   669  
   670  	utils.WriteResponse(w, http.StatusOK, reports)
   671  }
   672  
   673  // ImagesBatchRemove is the endpoint for batch image removal.
   674  func ImagesBatchRemove(w http.ResponseWriter, r *http.Request) {
   675  	runtime := r.Context().Value("runtime").(*libpod.Runtime)
   676  	decoder := r.Context().Value("decoder").(*schema.Decoder)
   677  	query := struct {
   678  		All    bool     `schema:"all"`
   679  		Force  bool     `schema:"force"`
   680  		Images []string `schema:"images"`
   681  	}{
   682  		All:   false,
   683  		Force: false,
   684  	}
   685  
   686  	if err := decoder.Decode(&query, r.URL.Query()); err != nil {
   687  		utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
   688  			errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String()))
   689  		return
   690  	}
   691  
   692  	opts := entities.ImageRemoveOptions{All: query.All, Force: query.Force}
   693  
   694  	imageEngine := abi.ImageEngine{Libpod: runtime}
   695  	rmReport, rmErrors := imageEngine.Remove(r.Context(), query.Images, opts)
   696  
   697  	strErrs := errorhandling.ErrorsToStrings(rmErrors)
   698  	report := handlers.LibpodImagesRemoveReport{ImageRemoveReport: *rmReport, Errors: strErrs}
   699  	utils.WriteResponse(w, http.StatusOK, report)
   700  }
   701  
   702  // ImagesRemove is the endpoint for removing one image.
   703  func ImagesRemove(w http.ResponseWriter, r *http.Request) {
   704  	runtime := r.Context().Value("runtime").(*libpod.Runtime)
   705  	decoder := r.Context().Value("decoder").(*schema.Decoder)
   706  	query := struct {
   707  		Force bool `schema:"force"`
   708  	}{
   709  		Force: false,
   710  	}
   711  
   712  	if err := decoder.Decode(&query, r.URL.Query()); err != nil {
   713  		utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
   714  			errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String()))
   715  		return
   716  	}
   717  
   718  	opts := entities.ImageRemoveOptions{Force: query.Force}
   719  	imageEngine := abi.ImageEngine{Libpod: runtime}
   720  	rmReport, rmErrors := imageEngine.Remove(r.Context(), []string{utils.GetName(r)}, opts)
   721  
   722  	// In contrast to batch-removal, where we're only setting the exit
   723  	// code, we need to have another closer look at the errors here and set
   724  	// the appropriate http status code.
   725  
   726  	switch rmReport.ExitCode {
   727  	case 0:
   728  		report := handlers.LibpodImagesRemoveReport{ImageRemoveReport: *rmReport, Errors: []string{}}
   729  		utils.WriteResponse(w, http.StatusOK, report)
   730  	case 1:
   731  		// 404 - no such image
   732  		utils.Error(w, "error removing image", http.StatusNotFound, errorhandling.JoinErrors(rmErrors))
   733  	case 2:
   734  		// 409 - conflict error (in use by containers)
   735  		utils.Error(w, "error removing image", http.StatusConflict, errorhandling.JoinErrors(rmErrors))
   736  	default:
   737  		// 500 - internal error
   738  		utils.Error(w, "failed to remove image", http.StatusInternalServerError, errorhandling.JoinErrors(rmErrors))
   739  	}
   740  }