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

     1  package compat
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"io"
     7  	"io/ioutil"
     8  	"net/http"
     9  	"os"
    10  	"strings"
    11  
    12  	"github.com/containers/buildah"
    13  	"github.com/containers/common/pkg/config"
    14  	"github.com/containers/image/v5/manifest"
    15  	"github.com/containers/podman/v2/libpod"
    16  	image2 "github.com/containers/podman/v2/libpod/image"
    17  	"github.com/containers/podman/v2/pkg/api/handlers"
    18  	"github.com/containers/podman/v2/pkg/api/handlers/utils"
    19  	"github.com/containers/podman/v2/pkg/auth"
    20  	"github.com/containers/podman/v2/pkg/domain/entities"
    21  	"github.com/docker/docker/api/types"
    22  	"github.com/gorilla/schema"
    23  	"github.com/opencontainers/go-digest"
    24  	"github.com/pkg/errors"
    25  )
    26  
    27  // mergeNameAndTagOrDigest creates an image reference as string from the
    28  // provided image name and tagOrDigest which can be a tag, a digest or empty.
    29  func mergeNameAndTagOrDigest(name, tagOrDigest string) string {
    30  	if len(tagOrDigest) == 0 {
    31  		return name
    32  	}
    33  
    34  	separator := ":" // default to tag
    35  	if _, err := digest.Parse(tagOrDigest); err == nil {
    36  		// We have a digest, so let's change the separator.
    37  		separator = "@"
    38  	}
    39  	return fmt.Sprintf("%s%s%s", name, separator, tagOrDigest)
    40  }
    41  
    42  func ExportImage(w http.ResponseWriter, r *http.Request) {
    43  	// 200 ok
    44  	// 500 server
    45  	runtime := r.Context().Value("runtime").(*libpod.Runtime)
    46  
    47  	name := utils.GetName(r)
    48  	newImage, err := runtime.ImageRuntime().NewFromLocal(name)
    49  	if err != nil {
    50  		utils.ImageNotFound(w, name, errors.Wrapf(err, "failed to find image %s", name))
    51  		return
    52  	}
    53  	tmpfile, err := ioutil.TempFile("", "api.tar")
    54  	if err != nil {
    55  		utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to create tempfile"))
    56  		return
    57  	}
    58  	defer os.Remove(tmpfile.Name())
    59  	if err := tmpfile.Close(); err != nil {
    60  		utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to close tempfile"))
    61  		return
    62  	}
    63  	if err := newImage.Save(r.Context(), name, "docker-archive", tmpfile.Name(), []string{}, false, false, true); err != nil {
    64  		utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to save image"))
    65  		return
    66  	}
    67  	rdr, err := os.Open(tmpfile.Name())
    68  	if err != nil {
    69  		utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to read the exported tarfile"))
    70  		return
    71  	}
    72  	defer rdr.Close()
    73  	utils.WriteResponse(w, http.StatusOK, rdr)
    74  }
    75  
    76  func PruneImages(w http.ResponseWriter, r *http.Request) {
    77  	var (
    78  		filters []string
    79  	)
    80  	decoder := r.Context().Value("decoder").(*schema.Decoder)
    81  	runtime := r.Context().Value("runtime").(*libpod.Runtime)
    82  
    83  	query := struct {
    84  		All     bool
    85  		Filters map[string][]string `schema:"filters"`
    86  	}{
    87  		// This is where you can override the golang default value for one of fields
    88  	}
    89  
    90  	if err := decoder.Decode(&query, r.URL.Query()); err != nil {
    91  		utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String()))
    92  		return
    93  	}
    94  
    95  	idr := []types.ImageDeleteResponseItem{}
    96  	for k, v := range query.Filters {
    97  		for _, val := range v {
    98  			filters = append(filters, fmt.Sprintf("%s=%s", k, val))
    99  		}
   100  	}
   101  	pruneCids, err := runtime.ImageRuntime().PruneImages(r.Context(), query.All, filters)
   102  	if err != nil {
   103  		utils.InternalServerError(w, err)
   104  		return
   105  	}
   106  	for _, p := range pruneCids {
   107  		idr = append(idr, types.ImageDeleteResponseItem{
   108  			Deleted: p,
   109  		})
   110  	}
   111  
   112  	// FIXME/TODO to do this exactly correct, pruneimages needs to return idrs and space-reclaimed, then we are golden
   113  	ipr := types.ImagesPruneReport{
   114  		ImagesDeleted:  idr,
   115  		SpaceReclaimed: 1, // TODO we cannot supply this right now
   116  	}
   117  	utils.WriteResponse(w, http.StatusOK, handlers.ImagesPruneReport{ImagesPruneReport: ipr})
   118  }
   119  
   120  func CommitContainer(w http.ResponseWriter, r *http.Request) {
   121  	var (
   122  		destImage string
   123  	)
   124  	decoder := r.Context().Value("decoder").(*schema.Decoder)
   125  	runtime := r.Context().Value("runtime").(*libpod.Runtime)
   126  
   127  	query := struct {
   128  		Author    string `schema:"author"`
   129  		Changes   string `schema:"changes"`
   130  		Comment   string `schema:"comment"`
   131  		Container string `schema:"container"`
   132  		// fromSrc   string  # fromSrc is currently unused
   133  		Pause bool   `schema:"pause"`
   134  		Repo  string `schema:"repo"`
   135  		Tag   string `schema:"tag"`
   136  	}{
   137  		// This is where you can override the golang default value for one of fields
   138  	}
   139  
   140  	if err := decoder.Decode(&query, r.URL.Query()); err != nil {
   141  		utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String()))
   142  		return
   143  	}
   144  	rtc, err := runtime.GetConfig()
   145  	if err != nil {
   146  		utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()"))
   147  		return
   148  	}
   149  	sc := image2.GetSystemContext(rtc.Engine.SignaturePolicyPath, "", false)
   150  	tag := "latest"
   151  	options := libpod.ContainerCommitOptions{
   152  		Pause: true,
   153  	}
   154  	options.CommitOptions = buildah.CommitOptions{
   155  		SignaturePolicyPath:   rtc.Engine.SignaturePolicyPath,
   156  		ReportWriter:          os.Stderr,
   157  		SystemContext:         sc,
   158  		PreferredManifestType: manifest.DockerV2Schema2MediaType,
   159  	}
   160  
   161  	input := handlers.CreateContainerConfig{}
   162  	if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
   163  		utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()"))
   164  		return
   165  	}
   166  
   167  	if len(query.Tag) > 0 {
   168  		tag = query.Tag
   169  	}
   170  	options.Message = query.Comment
   171  	options.Author = query.Author
   172  	options.Pause = query.Pause
   173  	options.Changes = strings.Fields(query.Changes)
   174  	ctr, err := runtime.LookupContainer(query.Container)
   175  	if err != nil {
   176  		utils.Error(w, "Something went wrong.", http.StatusNotFound, err)
   177  		return
   178  	}
   179  
   180  	// I know mitr hates this ... but doing for now
   181  	if len(query.Repo) > 1 {
   182  		destImage = fmt.Sprintf("%s:%s", query.Repo, tag)
   183  	}
   184  
   185  	commitImage, err := ctr.Commit(r.Context(), destImage, options)
   186  	if err != nil && !strings.Contains(err.Error(), "is not running") {
   187  		utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrapf(err, "CommitFailure"))
   188  		return
   189  	}
   190  	utils.WriteResponse(w, http.StatusOK, handlers.IDResponse{ID: commitImage.ID()}) // nolint
   191  }
   192  
   193  func CreateImageFromSrc(w http.ResponseWriter, r *http.Request) {
   194  	// 200 no error
   195  	// 404 repo does not exist or no read access
   196  	// 500 internal
   197  	decoder := r.Context().Value("decoder").(*schema.Decoder)
   198  	runtime := r.Context().Value("runtime").(*libpod.Runtime)
   199  
   200  	query := struct {
   201  		FromSrc string   `schema:"fromSrc"`
   202  		Changes []string `schema:"changes"`
   203  	}{
   204  		// This is where you can override the golang default value for one of fields
   205  	}
   206  
   207  	if err := decoder.Decode(&query, r.URL.Query()); err != nil {
   208  		utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String()))
   209  		return
   210  	}
   211  	// fromSrc – Source to import. The value may be a URL from which the image can be retrieved or - to read the image from the request body. This parameter may only be used when importing an image.
   212  	source := query.FromSrc
   213  	if source == "-" {
   214  		f, err := ioutil.TempFile("", "api_load.tar")
   215  		if err != nil {
   216  			utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to create tempfile"))
   217  			return
   218  		}
   219  		source = f.Name()
   220  		if err := SaveFromBody(f, r); err != nil {
   221  			utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to write temporary file"))
   222  		}
   223  	}
   224  	iid, err := runtime.Import(r.Context(), source, "", "", query.Changes, "", false)
   225  	if err != nil {
   226  		utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to import tarball"))
   227  		return
   228  	}
   229  	tmpfile, err := ioutil.TempFile("", "fromsrc.tar")
   230  	if err != nil {
   231  		utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to create tempfile"))
   232  		return
   233  	}
   234  	if err := tmpfile.Close(); err != nil {
   235  		utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to close tempfile"))
   236  		return
   237  	}
   238  	// Success
   239  	utils.WriteResponse(w, http.StatusOK, struct {
   240  		Status         string            `json:"status"`
   241  		Progress       string            `json:"progress"`
   242  		ProgressDetail map[string]string `json:"progressDetail"`
   243  		Id             string            `json:"id"` // nolint
   244  	}{
   245  		Status:         iid,
   246  		ProgressDetail: map[string]string{},
   247  		Id:             iid,
   248  	})
   249  
   250  }
   251  
   252  func CreateImageFromImage(w http.ResponseWriter, r *http.Request) {
   253  	// 200 no error
   254  	// 404 repo does not exist or no read access
   255  	// 500 internal
   256  	decoder := r.Context().Value("decoder").(*schema.Decoder)
   257  	runtime := r.Context().Value("runtime").(*libpod.Runtime)
   258  
   259  	query := struct {
   260  		FromImage string `schema:"fromImage"`
   261  		Tag       string `schema:"tag"`
   262  	}{
   263  		// This is where you can override the golang default value for one of fields
   264  	}
   265  
   266  	if err := decoder.Decode(&query, r.URL.Query()); err != nil {
   267  		utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String()))
   268  		return
   269  	}
   270  
   271  	fromImage := mergeNameAndTagOrDigest(query.FromImage, query.Tag)
   272  
   273  	authConf, authfile, key, err := auth.GetCredentials(r)
   274  	if err != nil {
   275  		utils.Error(w, "failed to retrieve repository credentials", http.StatusBadRequest, errors.Wrapf(err, "failed to parse %q header for %s", key, r.URL.String()))
   276  		return
   277  	}
   278  	defer auth.RemoveAuthfile(authfile)
   279  
   280  	registryOpts := image2.DockerRegistryOptions{DockerRegistryCreds: authConf}
   281  	if sys := runtime.SystemContext(); sys != nil {
   282  		registryOpts.DockerCertPath = sys.DockerCertPath
   283  	}
   284  	rtc, err := runtime.GetConfig()
   285  	if err != nil {
   286  		utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()"))
   287  		return
   288  	}
   289  	pullPolicy, err := config.ValidatePullPolicy(rtc.Engine.PullPolicy)
   290  	if err != nil {
   291  		utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()"))
   292  		return
   293  	}
   294  	img, err := runtime.ImageRuntime().New(r.Context(),
   295  		fromImage,
   296  		"", // signature policy
   297  		authfile,
   298  		nil, // writer
   299  		&registryOpts,
   300  		image2.SigningOptions{},
   301  		nil, // label
   302  		pullPolicy,
   303  	)
   304  	if err != nil {
   305  		utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err)
   306  		return
   307  	}
   308  
   309  	// Success
   310  	utils.WriteResponse(w, http.StatusOK, struct {
   311  		Status         string            `json:"status"`
   312  		Error          string            `json:"error"`
   313  		Progress       string            `json:"progress"`
   314  		ProgressDetail map[string]string `json:"progressDetail"`
   315  		Id             string            `json:"id"` // nolint
   316  	}{
   317  		Status:         fmt.Sprintf("pulling image (%s) from %s", img.Tag, strings.Join(img.Names(), ", ")),
   318  		ProgressDetail: map[string]string{},
   319  		Id:             img.ID(),
   320  	})
   321  }
   322  
   323  func GetImage(w http.ResponseWriter, r *http.Request) {
   324  	// 200 no error
   325  	// 404 no such
   326  	// 500 internal
   327  	name := utils.GetName(r)
   328  	newImage, err := utils.GetImage(r, name)
   329  	if err != nil {
   330  		// Here we need to fiddle with the error message because docker-py is looking for "No
   331  		// such image" to determine on how to raise the correct exception.
   332  		errMsg := strings.ReplaceAll(err.Error(), "no such image", "No such image")
   333  		utils.Error(w, "Something went wrong.", http.StatusNotFound, errors.Errorf("failed to find image %s: %s", name, errMsg))
   334  		return
   335  	}
   336  	inspect, err := handlers.ImageDataToImageInspect(r.Context(), newImage)
   337  	if err != nil {
   338  		utils.Error(w, "Server error", http.StatusInternalServerError, errors.Wrapf(err, "failed to convert ImageData to ImageInspect '%s'", inspect.ID))
   339  		return
   340  	}
   341  	utils.WriteResponse(w, http.StatusOK, inspect)
   342  }
   343  
   344  func GetImages(w http.ResponseWriter, r *http.Request) {
   345  	images, err := utils.GetImages(w, r)
   346  	if err != nil {
   347  		utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Failed get images"))
   348  		return
   349  	}
   350  	var summaries = make([]*entities.ImageSummary, len(images))
   351  	for j, img := range images {
   352  		is, err := handlers.ImageToImageSummary(img)
   353  		if err != nil {
   354  			utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Failed transform image summaries"))
   355  			return
   356  		}
   357  		summaries[j] = is
   358  	}
   359  	utils.WriteResponse(w, http.StatusOK, summaries)
   360  }
   361  
   362  func LoadImages(w http.ResponseWriter, r *http.Request) {
   363  	// TODO this is basically wrong
   364  	decoder := r.Context().Value("decoder").(*schema.Decoder)
   365  	runtime := r.Context().Value("runtime").(*libpod.Runtime)
   366  
   367  	query := struct {
   368  		Changes map[string]string `json:"changes"`
   369  		Message string            `json:"message"`
   370  		Quiet   bool              `json:"quiet"`
   371  	}{
   372  		// This is where you can override the golang default value for one of fields
   373  	}
   374  
   375  	if err := decoder.Decode(&query, r.URL.Query()); err != nil {
   376  		utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String()))
   377  		return
   378  	}
   379  
   380  	var (
   381  		err    error
   382  		writer io.Writer
   383  	)
   384  	f, err := ioutil.TempFile("", "api_load.tar")
   385  	if err != nil {
   386  		utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to create tempfile"))
   387  		return
   388  	}
   389  	if err := SaveFromBody(f, r); err != nil {
   390  		utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to write temporary file"))
   391  		return
   392  	}
   393  	id, err := runtime.LoadImage(r.Context(), "", f.Name(), writer, "")
   394  	if err != nil {
   395  		utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to load image"))
   396  		return
   397  	}
   398  	utils.WriteResponse(w, http.StatusOK, struct {
   399  		Stream string `json:"stream"`
   400  	}{
   401  		Stream: fmt.Sprintf("Loaded image: %s\n", id),
   402  	})
   403  }
   404  
   405  func ExportImages(w http.ResponseWriter, r *http.Request) {
   406  	// 200 OK
   407  	// 500 Error
   408  	decoder := r.Context().Value("decoder").(*schema.Decoder)
   409  	runtime := r.Context().Value("runtime").(*libpod.Runtime)
   410  
   411  	query := struct {
   412  		Names string `schema:"names"`
   413  	}{
   414  		// This is where you can override the golang default value for one of fields
   415  	}
   416  	if err := decoder.Decode(&query, r.URL.Query()); err != nil {
   417  		utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String()))
   418  		return
   419  	}
   420  	images := make([]string, 0)
   421  	images = append(images, strings.Split(query.Names, ",")...)
   422  	tmpfile, err := ioutil.TempFile("", "api.tar")
   423  	if err != nil {
   424  		utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to create tempfile"))
   425  		return
   426  	}
   427  	defer os.Remove(tmpfile.Name())
   428  	if err := tmpfile.Close(); err != nil {
   429  		utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to close tempfile"))
   430  		return
   431  	}
   432  	if err := runtime.ImageRuntime().SaveImages(r.Context(), images, "docker-archive", tmpfile.Name(), false, true); err != nil {
   433  		utils.InternalServerError(w, err)
   434  		return
   435  	}
   436  	rdr, err := os.Open(tmpfile.Name())
   437  	if err != nil {
   438  		utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to read the exported tarfile"))
   439  		return
   440  	}
   441  	defer rdr.Close()
   442  	utils.WriteResponse(w, http.StatusOK, rdr)
   443  }